处理令牌的寿命与受众
目标
亲自确认 Pod 身份如何签发、具有什么生命周期和受众,并应用完全不向不需要令牌的工作负载提供令牌的配置。
为什么重要
容器被攻破后,攻击者首先寻找的就是 /var/run/secrets/kubernetes.io/serviceaccount 中的令牌。因此,本模块的核心问题有两个——这个 Pod 真的需要令牌吗,以及该令牌何时过期。1.24 之前的方式对这两个问题都给出了糟糕答案。创建账号时会自动生成永不过期的令牌,Pod 无论是否需要都会挂载它。现在,Pod 获得的令牌具有生命周期,由 kubelet 续期,也可以通过 automountServiceAccountToken: false 彻底关闭。如果再加上 audience,令牌还会包含“用于何处”,从而防止为 Vault 签发的令牌在其他地方被重复使用。这一结构会直接延伸到云工作负载身份,形成彻底消除静态密钥的设计。最后请记住,创建身份并不等于获得权限。权限仍由 RBAC 决定。
步骤
- 创建命名空间
sa-lab,并在其中创建 ServiceAccountpayments。此账号不能关联自动生成的 Secret。然后在/root/ops/sa/out/sa-note.txt中用一行说明为何现代版本不再自动生成 Secret(需要包含 1.24 之后改用短期令牌的背景,以及过期、生命周期等表述)。 - 在
sa-lab中创建 Podpayments-api,将spec.serviceAccountName指定为payments。使用默认设置时,会自动附加包含服务账号令牌的 projected 卷。 - 创建 ServiceAccount
locked,设置automountServiceAccountToken: false。然后创建 Podhardened,将spec.serviceAccountName设为locked,将spec.automountServiceAccountToken设为false。此 Pod 不能附加任何令牌卷。 - 为
payments账号签发生命周期为 1 小时(3600秒)、受众为vault的令牌,并将该 JWT 的解码后载荷 JSON保存到/root/ops/sa/out/token.json。此 JSON 中的sub必须是system:serviceaccount:sa-lab:payments,aud不能为空,exp与iat的差约为 3600 秒,并且必须包含kubernetes.io声明。 - 在
sa-lab中创建 Podprojected-demo。将spec.serviceAccountName设为payments,在 projected 卷中添加serviceAccountToken源,并指定audience: vault、expirationSeconds: 3600、path: vault-token。第一个容器必须将该卷挂载到/var/run/secrets/vault路径。 - 在
sa-lab中创建 Secretpayments-legacy-token。type为kubernetes.io/service-account-token,注解kubernetes.io/service-account.name为payments。然后在/root/ops/sa/out/legacy-note.txt中用至少 60 字节、约两句话说明为何不建议使用此方式(必须提到没有过期时间,以及吊销和轮换负担)。 - 只向
payments账号授予最小权限。它必须能在sa-lab中对 ConfigMap 执行get和list,但不能读取 Secret,也不能删除 ConfigMap。使用以payments为主体的 RoleBinding 连接权限。 - 创建
/root/ops/sa/out/sa-audit.json。pods是数组,其元素数量必须与sa-lab中实际 Pod 数量完全一致。每个元素包含name、service_account、automount三个字段。payments-api的service_account必须为payments,hardened的automount必须为false,而且不能有任何service_account为default的 Pod。最后,在recommendations数组中写入至少两条改进建议。
参考
- 令牌签发命令为
kubectl create token <어카운트> -n sa-lab --duration=3600s --audience=vault。JWT 采用헤더.페이로드.서명结构,只需取出第二段并解码。base64url 的长度必须填充到 4 的倍数,因此使用python3的base64.urlsafe_b64decode很方便。 - 可以使用
kubectl get pod <이름> -n sa-lab -o jsonpath='{.spec.volumes}'检查 Pod 的卷结构。自动注入的令牌卷名称以kube-api-access-开头。 - 第 8 步的 Pod 列表最好从
kubectl get pods -n sa-lab -o json使用jq生成。未显式设置automount字段的 Pod 请按默认值(true)记录。 - 常见错误 1:第 3 步只在 Pod 中关闭,却不在账号中关闭。本实验会检查两个位置。
- 常见错误 2:第 5 步和第 3 步的 Pod 使用
default账号。这会在第 8 步审计中被发现。请为每个 Pod 指定专用账号。 - 常见错误 3:第 4 步将令牌字符串本身保存到文件。需要保存的是解码后的载荷 JSON。
- 实验 Pod 每次实验都会重新创建,因此前一实验创建的集群状态不会保留。请在本实验中亲自创建命名空间和账号。这正是运维流程必须通过运行手册和清单保存,而不能只靠记忆的原因。
创建专用服务账号
创建命名空间 sa-lab,并在其中创建 ServiceAccount payments。此账号不能关联自动生成的 Secret。然后在 /root/ops/sa/out/sa-note.txt 中用一行说明为何现代版本不再自动生成 Secret(需要包含 1.24 之后改用短期令牌的背景,以及过期、生命周期等表述)。
现代版本中,即使创建账号,也不会随之生成 Secret。请用一行总结为什么会改成这样。
为 Pod 指定专用账号
在 sa-lab 中创建 Pod payments-api,将 spec.serviceAccountName 指定为 payments。使用默认设置时,会自动附加包含服务账号令牌的 projected 卷。
请确认 Pod 规范中用于指定账号的字段名称。指定后,会自动附加令牌卷。
关闭令牌自动挂载
创建 ServiceAccount locked,设置 automountServiceAccountToken: false。然后创建 Pod hardened,将 spec.serviceAccountName 设为 locked,将 spec.automountServiceAccountToken 设为 false。此 Pod 不能附加任何令牌卷。
同一个字段既可用于 Pod,也可用于账号。关闭后,卷列表中不能残留令牌。
签发短期令牌并查看载荷
为 payments 账号签发生命周期为 1 小时(3600 秒)、受众为 vault 的令牌,并将该 JWT 的解码后载荷 JSON保存到 /root/ops/sa/out/token.json。此 JSON 中的 sub 必须是 system:serviceaccount:sa-lab:payments,aud 不能为空,exp 与 iat 的差约为 3600 秒,并且必须包含 kubernetes.io 声明。
JWT 是由点分隔的三段,中间一段是载荷。它采用 base64url 编码,需要补齐 padding 才能解码。通过两个时间值的差确认生命周期。
使用指定受众的 projected 令牌
在 sa-lab 中创建 Pod projected-demo。将 spec.serviceAccountName 设为 payments,在 projected 卷中添加 serviceAccountToken 源,并指定 audience: vault、expirationSeconds: 3600、path: vault-token。第一个容器必须将该卷挂载到 /var/run/secrets/vault 路径。
在令牌中写入受众后,它只对该受众有效。请准确匹配文件名和挂载路径。
创建旧式长期令牌 Secret 并总结风险
在 sa-lab 中创建 Secret payments-legacy-token。type 为 kubernetes.io/service-account-token,注解 kubernetes.io/service-account.name 为 payments。然后在 /root/ops/sa/out/legacy-note.txt 中用至少 60 字节、约两句话说明为何不建议使用此方式(必须提到没有过期时间,以及吊销和轮换负担)。
必须有特定类型和注解,控制器才会填充令牌。这种方式真正的问题不在于方便,而在于没有过期时间。
只为账号授予最小权限
只向 payments 账号授予最小权限。它必须能在 sa-lab 中对 ConfigMap 执行 get 和 list,但不能读取 Secret,也不能删除 ConfigMap。使用以 payments 为主体的 RoleBinding 连接权限。
创建账号与授予权限是两回事。只开放所需权限,并通过检查验证其余操作确实被阻止。
审计账号使用情况
创建 /root/ops/sa/out/sa-audit.json。pods 是数组,其元素数量必须与 sa-lab 中实际 Pod 数量完全一致。每个元素包含 name、service_account、automount 三个字段。payments-api 的 service_account 必须为 payments,hardened 的 automount 必须为 false,而且不能有任何 service_account 为 default 的 Pod。最后,在 recommendations 数组中写入至少两条改进建议。
Pod 数量必须与实际集群完全一致。不能有任何 Pod 使用 default 账号。