
ATT&CK TA0002 的防禦面——用 Kyverno 和 RBAC 從源頭擋住惡意執行。
| 攻擊 | 防禦措施 | 層級 |
|---|---|---|
| S04 Jupyter Shell | NetworkPolicy + 關閉不必要服務 | Network / Ops |
| S05 kubectl 控制叢集 | RBAC 最小權限 + automount=false | RBAC |
| S06 部署特權 Pod | Kyverno/PSS 阻擋 privileged | Admission |
| S07 CronJob 持久化 | RBAC 限制 CronJob 建立 + Audit | RBAC / Audit |
| S08 釣魚 Pod | 映像白名單 + 部署審核流程 | Admission / Ops |

RBAC 回答「你能不能建 Pod」,Admission Control 回答「你建的這個 Pod 安全嗎」。兩者缺一不可。
所有指令都在 koad 專案目錄下執行。如果還沒 clone,請先參考 Day 1 的 Step 0。
開始前請確認以下環境就緒。
# 主機終端
kubectl get pods -n kyverno
預期結果:
NAME READY STATUS RESTARTS AGE
kyverno-admission-controller-... 1/1 Running 0 ...
kyverno-background-controller-... 1/1 Running 0 ...
kyverno-cleanup-controller-... 1/1 Running 0 ...
kyverno-reports-controller-... 1/1 Running 0 ...
如果 Kyverno 未安裝,參考 Day 1 的部署指引安裝。
# 主機終端
kubectl get pods -n koad -l 'koad-scenario in (S04,S05,S06,S07,S08)'
防禦演練需要攻擊場景 Pod 就緒,才能驗證防禦前後的差異。
# 主機終端
kubectl get clusterpolicies | grep koad
應該看到
koad-block-privileged、koad-require-run-as-nonroot等策略。
完成本日實作後,你將能夠:
RBAC 控制「誰能做什麼操作」,但無法控制操作的內容。例如,一個使用者可能有建立 Pod 的權限,但 RBAC 無法阻止他建立 privileged: true 的 Pod。
這就是 Admission Controller 的工作:在 API Server 接收請求後、物件寫入 etcd 之前,檢查和修改請求內容。

| 項目 | Kyverno | OPA/Gatekeeper |
|---|---|---|
| 策略語言 | YAML(K8s 原生) | Rego(專用語言) |
| 學習曲線 | 低 | 高 |
| 功能 | Validate + Mutate + Generate + VerifyImages | 主要 Validate |
| 報告 | PolicyReport (CRD) | Constraint Status |
| 社群 | CNCF Graduated (2024) | CNCF Graduated |
| 效能 | 單一 webhook | 需要 OPA sidecar |
KOAD 選用 Kyverno 是因為策略用 YAML 寫,K8s 工程師不需要學新語言。
踩坑提醒:如果你的團隊已經在用 OPA/Gatekeeper,不需要遷移到 Kyverno。兩者甚至可以共存——Kyverno 做 Mutate,Gatekeeper 做 Validate。重點是「有策略引擎」而不是「用哪個」。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: koad-block-privileged
annotations:
policies.kyverno.io/title: Block Privileged Containers
policies.kyverno.io/category: Pod Security Standards (Baseline)
policies.kyverno.io/severity: high
policies.kyverno.io/description: >-
Privileged mode disables most security mechanisms and grants
near-root access to the host. Must be blocked in production.
spec:
validationFailureAction: Audit # Audit = 記錄但不阻擋
background: true # 掃描已存在的資源
rules:
- name: block-privileged
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Privileged containers are not allowed"
pattern:
spec:
containers:
- securityContext:
privileged: "!true"
如果切換到 Enforce 模式:
$ kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: test-priv
namespace: koad
spec:
containers:
- name: test
image: busybox
securityContext:
privileged: true
EOF
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
resource Pod/koad/test-priv was blocked due to the following policies:
koad-block-privileged: Privileged containers are not allowed
- name: block-hostpath
validate:
message: "HostPath volumes are not allowed"
pattern:
spec:
=(volumes):
- X(hostPath): "null"
原理深入:
=(volumes)表示「如果有 volumes 欄位才檢查」(條件錨點),X(hostPath)表示「hostPath 欄位不得存在」(否定錨點)。Kyverno 的錨點語法是它最強大也最容易搞混的特性。
- name: require-run-as-nonroot
validate:
message: "Containers must run as non-root"
pattern:
spec:
containers:
- securityContext:
runAsNonRoot: true
- name: require-trusted-registry
validate:
message: "Images must come from trusted registries"
pattern:
spec:
containers:
- image: "docker.io/* | gcr.io/* | registry.k8s.io/* | quay.io/*"
- name: drop-all-capabilities
validate:
message: "Containers must drop ALL capabilities"
pattern:
spec:
containers:
- securityContext:
capabilities:
drop: ["ALL"]
- name: block-host-namespaces
validate:
message: "Sharing host namespaces is not allowed"
pattern:
spec:
=(hostPID): false
=(hostIPC): false
=(hostNetwork): false
- name: require-readonly-rootfs
validate:
message: "Root filesystem must be read-only"
pattern:
spec:
containers:
- securityContext:
readOnlyRootFilesystem: true
除了 Validate(拒絕),Kyverno 還能 Mutate(自動修改):
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: koad-add-defaults
spec:
rules:
- name: add-security-defaults
match:
any:
- resources:
kinds: ["Pod"]
mutate:
patchStrategicMerge:
spec:
containers:
- (name): "*"
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
resources:
limits:
cpu: "500m"
memory: "512Mi"
防禦者視角:Mutate 策略可以「先放行再強化」——不阻擋開發者的部署,但自動加上安全預設值。這在推動安全策略的早期階段特別有用。
| 模式 | 行為 | 適用場景 |
|---|---|---|
| Audit | 記錄違規,不阻擋 | 初期導入、靶場環境、評估影響 |
| Enforce | 直接拒絕違規請求 | 生產環境、通過測試後 |
KOAD 使用 Audit 模式,因為攻擊場景本身就是「違規」的——我們需要它們能運行,同時記錄所有違規事件。
第 1 週:部署 Kyverno + 所有策略設為 Audit
↓
第 2-4 週:分析 PolicyReport,確認哪些違規是真的問題
↓
第 5 週:修復應用,讓它們符合策略
↓
第 6 週:將策略逐一切換到 Enforce
↓
持續:監控 PolicyReport + 調整策略
# 統計違規總數
$ kubectl get policyreport -n koad -o json | \
jq '[.items[].results[] | select(.result=="fail")] | length'
357
# 按規則分類統計
$ kubectl get policyreport -n koad -o json | \
jq '[.items[].results[] | select(.result=="fail")] |
group_by(.rule) | map({rule: .[0].rule, count: length})'
[
{"rule": "block-privileged", "count": 72},
{"rule": "require-run-as-nonroot", "count": 72},
{"rule": "drop-all-capabilities", "count": 72},
{"rule": "block-hostpath", "count": 45},
{"rule": "require-trusted-registry", "count": 36},
{"rule": "block-host-namespaces", "count": 33},
{"rule": "require-readonly-rootfs", "count": 27}
]
# 找出違規最多的 Pod
$ kubectl get policyreport -n koad -o json | \
jq '[.items[].results[] | select(.result=="fail")] |
group_by(.resources[0].name) |
map({pod: .[0].resources[0].name, violations: length}) |
sort_by(-.violations) | .[0:5]'
357 筆違規——這就是攻擊場景的真實面貌。
在 KOAD 的攻擊鏈中,attacker pod 之所以能控制整個叢集,根本原因是 SA 被綁定了 cluster-admin:
$ kubectl auth can-i --list
Resources Non-Resource URLs Resource Names Verbs
*.* [] [] [*]
*.* + [*] = 對所有資源的所有操作都有權限。
| 場景 | 使用 | 原因 |
|---|---|---|
| 應用只在自己的 Namespace 運行 | Role + RoleBinding | 最小範圍 |
| 需要跨 Namespace 讀取資源 | ClusterRole + RoleBinding | ClusterRole 可重用,RoleBinding 限制範圍 |
| 需要存取 cluster-scoped 資源 | ClusterRole + ClusterRoleBinding | Node, PV 等是 cluster-scoped |
| CI/CD 部署工具 | ClusterRole + RoleBinding (per NS) | 只給需要部署的 Namespace |
踩坑提醒:很多人以為 ClusterRole 一定是叢集範圍的權限。錯了!ClusterRole 搭配 RoleBinding 使用時,權限只限於 RoleBinding 所在的 Namespace。ClusterRole 只是「可以被多個 Namespace 重用的角色定義」。
# 1. 建立專用 Role(不是 ClusterRole)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: webapp-role
namespace: production
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"] # ← 只能讀取 Pod
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"] # ← 只能讀取 ConfigMap
# 注意:沒有 secrets、沒有 create/delete
# 2. 綁定到專用 SA
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: webapp-binding
namespace: production
subjects:
- kind: ServiceAccount
name: webapp-sa
namespace: production
roleRef:
kind: Role
name: webapp-role
apiGroup: rbac.authorization.k8s.io
以下權限組合可以直接導致叢集被控制:
| 權限 | 危險程度 | 為什麼 |
|---|---|---|
create pods |
高 | 可建立特權 Pod → 逃逸 |
create pods/exec |
高 | 可在任意 Pod 內執行指令 |
get secrets |
高 | 可讀取敏感資料 / SA Token |
create clusterrolebindings |
危急 | 可提權到 cluster-admin |
create serviceaccounts + create rolebindings |
高 | 可建立新 SA 並賦予權限 |
update daemonsets |
中 | 可在所有 Node 部署惡意容器 |
create mutatingwebhookconfigurations |
危急 | 可攔截所有 API 請求 |
escalate (verbs) |
危急 | 可賦予自己沒有的權限 |
大部分應用不需要存取 K8s API。關閉自動掛載可以讓攻擊者即使進入容器也拿不到 SA Token:
apiVersion: v1
kind: ServiceAccount
metadata:
name: webapp-sa
automountServiceAccountToken: false # ← 關閉自動掛載
或在 Pod 層級:
apiVersion: v1
kind: Pod
spec:
automountServiceAccountToken: false
優先順序:如果 SA 設為
false但 Pod 設為true,Pod 層級的設定會勝出。反之亦然。建議在 SA 層級設為false,只在需要的 Pod 中開啟。
#!/bin/bash
# rbac-audit.sh — 快速審計叢集中的高風險 RBAC 配置
echo "=== 綁定到 cluster-admin 的所有主體 ==="
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") |
{name: .metadata.name, subjects: .subjects}'
echo ""
echo "=== 有 secrets 讀取權限的角色 ==="
kubectl get clusterroles -o json | \
jq '.items[] | select(.rules[]? |
(.resources // [] | index("secrets")) and
(.verbs // [] | (index("get") or index("list") or index("watch")))
) | .metadata.name'
echo ""
echo "=== 有 pods/exec 權限的角色 ==="
kubectl get clusterroles -o json | \
jq '.items[] | select(.rules[]? |
(.resources // [] | index("pods/exec")) and
(.verbs // [] | index("create"))
) | .metadata.name'
echo ""
echo "=== 仍在自動掛載 Token 的 SA ==="
kubectl get sa -A -o json | \
jq '.items[] | select(
(.automountServiceAccountToken == true) or
(.automountServiceAccountToken == null)
) | {name: .metadata.name, ns: .metadata.namespace}'
echo ""
echo "=== 非 kube-system 中的 ClusterRoleBinding ==="
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.subjects[]? |
.namespace != "kube-system" and .namespace != null
) | {name: .metadata.name, role: .roleRef.name,
subject: .subjects[0].name, ns: .subjects[0].namespace}'
K8s 1.25+ 內建 Pod Security Standards,定義了三個層級:
| 層級 | 限制 | 適用 |
|---|---|---|
| Privileged | 無限制 | 系統元件(kube-system) |
| Baseline | 阻擋已知的危險配置 | 一般工作負載 |
| Restricted | 最嚴格的安全限制 | 敏感工作負載 |
| 模式 | Label | 行為 |
|---|---|---|
| enforce | pod-security.kubernetes.io/enforce |
拒絕不合規的 Pod |
| warn | pod-security.kubernetes.io/warn |
允許但在回應中加警告 |
| audit | pod-security.kubernetes.io/audit |
允許但寫入 Audit Log |
# 在 Namespace 上設定 label
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
# 這個 Pod 在 restricted namespace 中會被拒絕:
apiVersion: v1
kind: Pod
metadata:
name: will-be-rejected
spec:
containers:
- name: app
image: nginx
# 缺少以下必要配置:
# 必須包含的 securityContext:
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
Restricted 層級的完整限制:
privileged: true
hostPID, hostIPC, hostNetwork
hostPath VolumerunAsNonRoot: true
/proc mount type 為 DefaultseccompProfile: RuntimeDefault 或 Localhost
allowPrivilegeEscalation: false
| 面向 | PSS | Kyverno |
|---|---|---|
| 內建 | 是(K8s 原生) | 否(需要安裝) |
| 自訂性 | 只有三個預設層級 | 完全自訂 |
| Mutate | 不支援 | 支援自動修正 |
| 映像策略 | 不支援 | 支援映像白名單/簽章 |
| 報告 | 只有 Audit Log | PolicyReport CRD |
建議:用 PSS 做基線保護(Baseline 或 Restricted),用 Kyverno 做進階策略(映像白名單、自訂驗證、Mutate)。兩者不衝突,可以疊加使用。
K8s 1.30 正式 GA 了 ValidatingAdmissionPolicy——用 CEL(Common Expression Language)表達式直接在 API Server 內執行驗證,不需要外部 Webhook。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: deny-privileged-containers
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "!object.spec.containers.exists(c, c.securityContext.privileged == true)"
message: "Privileged containers are not allowed"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: deny-privileged-binding
spec:
policyName: deny-privileged-containers
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
environment: production
VAP vs Kyverno 比較:
| 面向 | ValidatingAdmissionPolicy | Kyverno |
|---|---|---|
| 來源 | K8s 原生(1.30 GA) | 第三方 CRD |
| 表達式語言 | CEL | YAML 錨點 + JMESPath |
| 效能 | 極高(in-process,無 Webhook 延遲) | 中(Webhook 網路往返) |
| Mutate | 不支援 | 支援 |
| Generate | 不支援 | 支援 |
| 學習曲線 | CEL 語法需學習 | YAML 直覺但錨點語法複雜 |
趨勢:VAP 適合高效能的簡單驗證規則;Kyverno 適合需要 Mutate、Generate、PolicyReport 的複雜場景。生產環境建議混用——VAP 處理高頻關鍵規則,Kyverno 處理進階策略。
| 考點 | 本日內容 | 權重 |
|---|---|---|
| RBAC 配置 | Role/RoleBinding vs ClusterRole/ClusterRoleBinding | Cluster Hardening 15% |
| SA 管理 | automountServiceAccountToken: false | Cluster Hardening 15% |
| Admission Control | Kyverno / OPA / PSS | Minimize Microservice 20% |
| Pod Security Standards | enforce/warn/audit label | Minimize Microservice 20% |
題目:設定
productionNamespace 使用 Restricted 層級的 Pod Security Standards,並確認一個 privileged Pod 無法在該 Namespace 中建立。
參考答案:
# 1. 設定 PSS label kubectl label namespace production \ pod-security.kubernetes.io/enforce=restricted # 2. 嘗試建立 privileged Pod kubectl run test --image=nginx -n production \ --overrides='{"spec":{"containers":[{"name":"test","image":"nginx","securityContext":{"privileged":true}}]}}' # 3. 預期結果:被拒絕 # Error: pods "test" is forbidden: violates PodSecurity "restricted:latest"
KOAD 攻擊鏈測試的結果完美說明了防禦的有效性:
| 防禦層 | 效果 | 阻擋位置 |
|---|---|---|
| NetworkPolicy default-deny-all | 阻斷整條攻擊鏈 | 網路層:attacker pod 無法發出任何請求 |
| Kyverno Audit | 記錄 357 筆違規 | Admission 層:72 特權容器 + 72 未禁用 automount + ... |
| RBAC 最小權限 | S05 kubectl 拿不到 cluster-admin | 授權層:攻擊在第一步就停了 |
| PSS Restricted | S06 特權 Pod 根本無法建立 | Admission 層:API Server 直接拒絕 |
| automount=false | 容器內無 SA Token | 認證層:即使入侵也無法存取 API |

縱深防禦的核心:每一層都能獨立擋住攻擊。 即使 RBAC 失守,Kyverno 仍然可以攔截;即使 Kyverno 被繞過,NetworkPolicy 仍然有效。
如果你需要還原環境以便重新操作攻擊場景:
# 主機終端 — 移除 PSS label
kubectl label namespace koad \
pod-security.kubernetes.io/enforce- \
pod-security.kubernetes.io/warn- \
pod-security.kubernetes.io/audit-
# 將 Kyverno 策略切回 Audit 模式(如果改過為 Enforce)
kubectl get clusterpolicies -o name | xargs -I{} \
kubectl patch {} --type merge -p '{"spec":{"validationFailureAction":"Audit"}}'
KOAD 的 Kyverno 策略預設為 Audit 模式,不影響攻擊場景運行。只有在你手動切到 Enforce 後才需要還原。
K8s 1.28 引入了 Native Sidecar Containers(正式名稱為 Sidecar Containers),在 1.29 進入 Beta、1.33 正式 GA。透過在 initContainers 中設定 restartPolicy: Always,該容器會在 init 階段啟動後持續運行,與主容器並行。
傳統做法是將 sidecar 放在 containers 陣列中,與主容器並列:
Completed
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
initContainers:
- name: log-forwarder
image: fluent/fluent-bit:3.0
restartPolicy: Always # 關鍵:讓 initContainer 持續運行
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
resources:
limits:
cpu: "100m"
memory: "64Mi"
containers:
- name: app
image: myapp:v1.0
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
| 面向 | 傳統 Sidecar | Native Sidecar |
|---|---|---|
| 啟動順序 | 無保證 | init 階段啟動,保證先於主容器 |
| 獨立 securityContext | 可以 | 可以(且生命週期更可控) |
| Job 相容性 | Pod 無法正常完成 | 主容器結束後 sidecar 自動終止 |
| 資源隔離 | 與主容器共享 QoS 計算 | 獨立計算,不影響主容器的 QoS |
Native Sidecar 的安全價值在於:可以為 log forwarder、mTLS proxy(如 Istio sidecar)等輔助容器設定獨立且更嚴格的 securityContext,同時保證啟動順序——確保安全元件在應用啟動前就緒。
automountServiceAccountToken: false 切斷 Token 來源automountServiceAccountToken: false 並驗證 Pod 無法讀取 SA Tokencluster-admin,定期用審計腳本檢查防禦 Execution 的關鍵洞察:攻擊者用的都是合法的 K8s API 操作。你不能靠防火牆或 EDR 擋住這些操作——只能靠 RBAC + Admission Control + 最小權限設計。
明天 Day 6 進入 TA0003 Persistence——五種持久化手法:RBAC 竄改、後門 SA、Sidecar 注入、映像植入、StaticPod。