LabHub
学习 学习路径 课程

CKS — Kubernetes 安全专家

收窄 RBAC 并切断令牌

在 LabHub 中继续学习

目标

亲自在集群中找出权限过大的角色,用最小权限 Role 取代它们,再通过询问 API 服务器验证结果。同时关闭 ServiceAccount 令牌自动进入 Pod 的所有路径。

为什么重要

RBAC 决定“谁能做什么”,但人类要阅读并验证它却非常困难。只要一个 ClusterRole 中混入 resources: ["*"],该角色就会连未来新增的 CRD 也全部涵盖,而可能无人察觉。因此,加固工作的真正起点不是“写好策略”,而是“列出当前究竟允许了什么”。

第二条主线是凭据。Pod 默认会获得令牌,即使应用完全不使用 API 也会获得。一旦某个 Pod 被攻破,攻击者就等于立即得到一份集群凭据。automountServiceAccountToken: false 虽然只有一行,却能大幅缩小入侵后的横向扩散范围。

步骤

  1. 创建命名空间 cks-rbac,并在其中创建 ServiceAccount report-sa
  2. 在集群所有 ClusterRole 中,筛选 rules[].verbs 包含 * 的角色名称, 按字典序排序后逐行保存到 /root/cks-rbac-hardening/wildcard-roles.txt
  3. 创建 ClusterRole cks-pod-reader。apiGroups 只有核心组(空字符串), resources 为 podspods/log,verbs 为 getlistwatch。 任何字段都不得包含 *
  4. 在集群所有 ClusterRole 中,筛选 verbs 至少包含 escalatebindimpersonate 之一的 角色名称,排序后保存到 /root/cks-rbac-hardening/danger-verb-roles.txt
  5. cks-rbac 命名空间中,通过 RoleBinding report-sa-pod-readerreport-sa 绑定 ClusterRole cks-pod-reader。然后把以下两个问题的答案(yesno) 按顺序分两行保存到 /root/cks-rbac-hardening/can-i.txt。 第一行:report-sa 能否在 cks-rbac 中 list pods; 第二行:report-sa 能否在 cks-rbac 中 get secrets。
  6. cks-rbac 中创建 Pod report(镜像 nginx:1.27-alpine)。serviceAccountNamereport-sa,Pod 规范的 automountServiceAccountTokenfalse
  7. cks-rbac 中创建 Secret report-sa-token。类型为 kubernetes.io/service-account-token,注解 kubernetes.io/service-account.name 的值为 report-sa。再创建 Pod token-app(镜像 nginx:1.27-alpine,serviceAccountName 为 report-sa),但通过 projected 卷获取令牌。卷名称为 api-tokenserviceAccountToken 源的 audienceapiexpirationSeconds3600pathtoken
  8. cks-rbac 中的 default ServiceAccount 设置 automountServiceAccountToken: false, 并将 default SA 无法在该命名空间中 list pods 的结果(no) 单独一行保存到 /root/cks-rbac-hardening/default-sa-can-i.txt

参考

命名空间与专用 ServiceAccount

创建命名空间 cks-rbac,并在其中创建 ServiceAccount report-sa

使用 kubectl create serviceaccount 创建。如果没有先创建命名空间,SA 创建会失败。

找出使用通配符的 ClusterRole

在集群所有 ClusterRole 中,筛选 rules[].verbs 包含 * 的角色名称, 按字典序排序后逐行保存到 /root/cks-rbac-hardening/wildcard-roles.txt

使用 jq 遍历 kubectl get clusterrole -o json,筛选 rules 中 verbs 包含 * 的对象。结果只保存名称,每行一个。

编写收窄权限的 ClusterRole

创建 ClusterRole cks-pod-reader。apiGroups 只有核心组(空字符串), resources 为 podspods/log,verbs 为 getlistwatch。 任何字段都不得包含 *

可以使用 kubectl create clusterrole --resource= --verb= 生成草稿。apiGroups、resources、verbs 中都不要使用通配符。

筛选包含危险动词的角色

在集群所有 ClusterRole 中,筛选 verbs 至少包含 escalatebindimpersonate 之一的 角色名称,排序后保存到 /root/cks-rbac-hardening/danger-verb-roles.txt

目标是包含 escalate、bind、impersonate 三个动词中任意一个的 ClusterRole。使用与第 02 步相同的方法,只保存排序后的名称。

使用 auth can-i 验证结果

cks-rbac 命名空间中,通过 RoleBinding report-sa-pod-readerreport-sa 绑定到 ClusterRole cks-pod-reader。然后把以下两个问题的答案(yesno) 按顺序分两行保存到 /root/cks-rbac-hardening/can-i.txt。 第一行:report-sa 能否在 cks-rbac 中 list pods; 第二行:report-sa 能否在 cks-rbac 中 get secrets。

使用 --as=system:serviceaccount:<네임스페이스>:<이름> 格式模拟主体。输出只有一行 yesno,可直接汇总到文件。

关闭 Pod 的令牌自动挂载

cks-rbac 中创建 Pod report(镜像 nginx:1.27-alpine)。serviceAccountNamereport-sa,Pod 规范的 automountServiceAccountTokenfalse

automountServiceAccountToken 是 Pod 规范顶层字段(直接位于 spec 下),不在容器内部。

手动令牌 Secret 与 projected 令牌

cks-rbac 中创建 Secret report-sa-token。类型为 kubernetes.io/service-account-token,注解 kubernetes.io/service-account.name 的值为 report-sa。再创建 Pod token-app(镜像 nginx:1.27-alpine,serviceAccountName 为 report-sa),但通过 projected 卷获取令牌。卷名称为 api-tokenserviceAccountToken 源的 audienceapiexpirationSeconds3600pathtoken

手动令牌 Secret 的类型是 kubernetes.io/service-account-token,并通过 kubernetes.io/service-account.name 注解指定所有者。另一侧则在 Pod 的 projected 卷中使用 serviceAccountToken 源,并设置 audience 和过期时间。

禁用 default ServiceAccount

cks-rbac 中的 default ServiceAccount 设置 automountServiceAccountToken: false, 并将 default SA 无法在该命名空间中 list pods 的结果(no) 单独一行保存到 /root/cks-rbac-hardening/default-sa-can-i.txt

使用 kubectl patch serviceaccount default -n <네임스페이스> -p '...' 一行即可完成。然后通过 can-i 确认 default SA 确实没有任何权限。