使用 RBAC 保护 Kubernetes 访问

KubernetesBeginner
立即练习

简介

Kubernetes 身份认证用于确认请求方的身份;授权则决定该身份可以执行哪些操作。RBAC 将权限表示为针对 API 资源的操作动词,并将这些权限绑定到用户、用户组或 ServiceAccount。

你将创建一个限定在命名空间内的只读身份,将其与相应权限关联起来,然后通过身份模拟验证允许和拒绝的访问边界。

理解 RBAC 时,可以将其归纳为三个问题:谁?(ServiceAccount)、可以做什么?(Role)以及谁获得这些权限?(RoleBinding)。只创建其中一个或两个对象,并不能完成完整的授权链路。

创建 ServiceAccount 身份

在本步骤中,为应用组件创建一个限定在命名空间内的 ServiceAccount。

ServiceAccount 是 Kubernetes API 中供工作负载和自动化任务使用的身份。它并不是容器内的 Linux 用户。为本示例创建独立身份,可以避免继承命名空间中的 default ServiceAccount。

cd /home/labex/project/rbac-access
kubectl create serviceaccount inventory-reader -n practice-rbac
kubectl get serviceaccount inventory-reader -n practice-rbac -o yaml

它的完整身份名称是 system:serviceaccount:practice-rbac:inventory-reader。单独创建身份不会赋予任何应用权限。

在 YAML 输出中,元数据会确认命名空间和名称。不要期待系统自动生成一个长期有效的令牌 Secret;现代 Kubernetes 项目会在 Pod 需要时,将短期令牌挂载到 Pod 中。

返回的对象包含专用名称和命名空间,但没有自动生成的令牌 Secret:

kind: ServiceAccount
metadata:
  name: inventory-reader
  namespace: practice-rbac

定义命名空间级权限

在本步骤中,创建一个 Role,使其仅允许在 practice-rbac 中读取 Pod 和 ConfigMap。

每条规则由 API 组、资源和操作动词组成。对于 Pod 和 ConfigMap 等核心资源,apiGroups: [""] 使用空字符串表示核心 API 组。get、list 和 watch 分别表示读取单个对象、读取对象集合以及监听资源变化。

cd /home/labex/project/rbac-access
cat <<'EOF' > inventory-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: inventory-reader, namespace: practice-rbac}
rules:
  - apiGroups: [""]
    resources: [pods, configmaps]
    verbs: [get, list, watch]
EOF

应用并查看 Role。与原始 YAML 相比,describe 会以更便于审查的表格形式展示策略规则:

kubectl apply -f inventory-role.yaml
kubectl describe role inventory-reader -n practice-rbac

空 API 组用于选择核心资源。Role 永远不会授予命名空间之外的访问权限。

同时注意其中缺少的权限:没有 create、update、patch 或 delete。最小权限原则不仅体现在授予了哪些权限,也体现在明确省略了哪些权限。

Resources   Non-Resource URLs  Resource Names  Verbs
---------   -----------------  --------------  -----
configmaps  []                 []              [get list watch]
pods        []                 []              [get list watch]

该表将 YAML 中合并定义的规则展开为每个资源一行。两行都具有相同的三个只读操作动词。

将 Role 绑定到身份

在本步骤中,使用 RoleBinding 将 Role 关联到 ServiceAccount。

由于两个对象都已经存在,因此命令式操作命令非常简洁。--role 指定权限来源,--serviceaccount 使用 namespace:name 格式标识主体。

cd /home/labex/project/rbac-access
kubectl create rolebinding inventory-reader -n practice-rbac --role=inventory-reader --serviceaccount=practice-rbac:inventory-reader
kubectl get rolebinding inventory-reader -n practice-rbac -o yaml

roleRef 指定权限,subjects 指定获得这些权限的身份。在这里,两者都限定在命名空间范围内。

即使 Role 和 ServiceAccount 使用相同名称,在创建此绑定之前,它们仍然互不相关。名称只是方便人类识别;RoleBinding 才是真正建立授权关系的对象。

在 YAML 中,沿着 roleRef.name: inventory-reader 追踪这条关联:主体的类型是 ServiceAccount,名称是 inventory-reader,命名空间是 practice-rbac。

测试允许和拒绝的操作

在本步骤中,使用带有 --as 参数的 kubectl auth can-i 来模拟该 ServiceAccount。

授权测试既需要验证允许的操作,也需要验证拒绝的操作。返回 yes 说明所需工作可以执行;返回 no 则说明系统没有意外授予破坏性操作或跨命名空间访问权限。

cd /home/labex/project/rbac-access
IDENTITY=system:serviceaccount:practice-rbac:inventory-reader
kubectl auth can-i list pods -n practice-rbac --as="$IDENTITY"
kubectl auth can-i get configmaps -n practice-rbac --as="$IDENTITY"
kubectl auth can-i delete pods -n practice-rbac --as="$IDENTITY" || true
kubectl auth can-i list pods -n default --as="$IDENTITY" || true

前两个结果应为 yes;删除 Pod 和跨命名空间列出 Pod 的结果应为 no。身份模拟可以在不暴露或创建令牌的情况下测试授权。

kubectl auth can-i 在返回 no 的同时,也会返回非零的 Shell 状态码。末尾的两个 || true 用于让这两个预期的拒绝测试继续执行;它们不会改变授权决策。

yes
yes
no
no

将这些结果理解为:前两个表示所需的能力,后两个表示受到保护的边界。只有 yes 结果的测试,无法证明系统遵循了最小权限原则。

当前实验用户被允许模拟此 ServiceAccount,以便进行测试。--as 只会改变 API 服务器针对当前命令评估的身份;它不会改变你的 Shell 用户,也不会永久切换 kubectl 凭据。

总结

你创建了一个 ServiceAccount,使用 Role 表达了命名空间级的只读权限,通过 RoleBinding 分配这些权限,并使用身份模拟验证了允许和拒绝的操作。