跳转至

Kubernetes RBAC 权限与安全

作者:JiangChong | 撰写时间:2026年07月

学完本文你将能够: ✅ 区分认证和授权的概念及在 K8s 中的分工 ✅ 使用 Role、ClusterRole、RoleBinding 实现权限控制 ✅ 理解 ServiceAccount 的工作原理和 Token 机制 ✅ 配置 Pod Security Admission 和 Secret 加密

前置阅读: Kubernetes 核心架构 + Kubernetes Pod 与 Deployment 实战


0 本文概览

学完 02 篇你会部署应用了,但如果你把集群给别人用,别人也能把你的应用删掉?

K8s 安全的核心是回答三个问题:

kubectl delete pod --all   → 谁能执行这个命令?        → 认证(你是谁)
                           → 他有没有权限这样做?      → 授权 / RBAC(你能做什么)
                           → Pod 自己想调用 API 呢?  → ServiceAccount(Pod 的代表证)

本文沿这个线索,从「你第一次被 403 Forbidden 拦住」开始,到「给团队设计一套完整 RBAC」结束。

一个不完美的但好理解的类比: K8s 的安全像一栋写字楼。认证是前台查工牌(你叫什么名字),授权是门禁系统(你能进 3 楼的研发区,但进不了 5 楼的财务室),ServiceAccount 是给你桌上那台自动打卡机办的「机器工牌」——它不是人,但需要权限去调用打卡系统。


1 认证 vs 授权:先搞清楚这两个概念

很多人在学习 K8s 安全时最先搞混的就是"认证"和"授权"。它们本质上是两个不同的步骤:

认证(Authentication)= 门卫检查你的身份证(你是谁) 授权(Authorization)= 门禁系统决定你能进哪些房间(你能做什么)

具体来说:

  • 认证回答的问题是:"你是不是你声称的那个人?"——凭密码、证书、Token 来证明身份。
  • 授权回答的问题是:"你被允许做什么?"——即使证明了你就是张三,张三也不一定能进 CEO 办公室。

在 K8s 中,API Server 收到请求后先做认证(确认身份),再做授权(检查权限)。认证失败返回 401,认证通过但授权不足返回 403。


2 为什么 K8s 需要安全配置

默认情况下,新安装的 K8s 集群不会主动拦截请求——匿名用户虽然能连接 API Server,但 RBAC 默认只给了 system:public-info-viewer 等极少权限,实际能做的非常有限。真正的风险在于:一旦有人拿到合法的客户端证书或 ServiceAccount Token,在没有额外 RBAC 配置的情况下,他可以操作的资源取决于默认绑定。

这留下了严重的安全隐患:

  • 恶意 Pod 可能通过 API 窃取集群信息
  • 被攻破的应用容器可能擅自修改集群资源
  • 内部人员可能误操作删除生产环境资源

因此,我们需要一套权限体系来回答三个问题:

  1. 可以访问 API Server?→ 认证
  2. 能做什么操作(get、create、delete)?→ 授权(RBAC)
  3. 能操作哪些资源(Pod、Service、Secret)?→ 授权(RBAC)

3 认证(Authentication)

先解决「你是谁」的问题——认证是授权的前提,只有确认了身份,才能判断这个身份能做什么。

Kubernetes 本身不管理用户身份,它信任认证代理提供的信息。

K8s 里的 User 到底是什么?——一个容易踩的认知陷阱

如果你来自 Vertica 背景,先忘掉你对「用户」的直觉理解。来看对比:

| | Vertica | Kubernetes |

|---|---|---------|

| 用户存在哪? | 存储在数据库 Catalog 中(v_catalog.users 可见) | 不存在于集群中 — 没有 kubectl get users 这种命令 |

| 怎么创建? | CREATE USER alice; — 创建的是一个持久化的数据库对象 | 无法创建 — 没有 CREATE USER 这种操作 |

| 用户来源? | 数据库自己管理用户名和密码 | 从外部认证系统提取一个字符串作为用户名 |

| 密码存储? | 加密存在数据库内部 | K8s 不存储用户密码,密码验证由外部系统完成 |

| 类比 | 小区的住户登记系统(业委会自己维护) | 访客系统(看的是你带来的身份证/工牌,不建立你自己的档案) |

在 K8s 中,User 只是一个字符串——它来自 X509 证书的 /CN= 字段、OIDC token 的 email claim、或者 kubectl 命令行指定的 --user=。API Server 做完认证后把这个字符串送给 RBAC 鉴权层,RBAC 只知道「有个叫 zhangsan 的人想删 Pod」,至于 zhangsan 是谁、密码对不对——那是认证模块的事,RBAC 不管。

这不是 K8s 的设计缺陷,而是刻意设计:K8s 把身份管理外包给企业现有的认证系统(LDAP、OIDC SSO、云 IAM),自己只做鉴权决策。反过来,ServiceAccount 是例外——它确实是存在 K8s 中的一等公民(kubectl get serviceaccount 可见),专门给 Pod 用。

支持以下认证方式:

  • X509 客户端证书:双向 TLS 认证,多见于 kubelet 与 API Server 之间
  • ServiceAccount Token:Pod 级别身份认证,最常用
  • Bootstrap Token:kubeadm 集群引导时使用的临时令牌(v1.18+ 稳定)
  • OpenID Connect(OIDC):企业 SSO 集成
  • Webhook Token 认证:通过外部服务验证 Bearer Token(如 Vault、Cloud IAM)
  • 认证代理(Authenticating Proxy):在 API Server 前放置认证代理,转发认证结果
  • 匿名请求:未认证用户的请求(默认放行至鉴权阶段,由 RBAC 决定)

3.1 ServiceAccount Token

这是 K8s 中最常用的认证方式之一,用于 Pod 级别的身份认证。每个 Namespace 自动拥有 default ServiceAccount。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: vertica-operator
  namespace: vertica-system
---
# 在 Pod 中指定
apiVersion: v1
kind: Pod
spec:
  serviceAccountName: vertica-operator

Token 自动挂载到 Pod 的 /var/run/secrets/kubernetes.io/serviceaccount/token

v1.24+ 变更:ServiceAccount 不再自动创建基于 Secret 的传统长期令牌。改为通过 TokenRequest API 创建带过期时间的、投射(projected)令牌卷,token 会自动续期。这一变化显著提升了安全性 — 令牌不再永久有效,支持受众(audience)绑定。

最佳实践: 每个应用使用独立的 ServiceAccount,不要使用 Namespace 的 default。

3.2 OpenID Connect(OIDC)

企业生产环境最常用的认证方式,与 SSO 系统集成:

# 配置 API Server 启动参数
--oidc-issuer-url=https://accounts.google.com
--oidc-client-id=kubernetes
--oidc-username-claim=email
--oidc-groups-claim=groups

使用 kubelogin 插件实现 OIDC 交互式登录:

kubectl oidc-login setup --oidc-issuer-url=https://my-idp.example.com \
  --oidc-client-id=k8s-client

3.3 Webhook Token 认证

专业场景:通过外部服务认证 Bearer Token。常用于集成 Vault、Cloud IAM。

3.4 默认绑定

K8s 启动后自动创建以下 ClusterRoleBinding,构成安全基线:

绑定 目标 说明
cluster-adminsystem:masters 超级管理员 拥有所有权限
system:discovery → 所有已认证用户 基本发现 可查看 API 列表和部分非敏感信息
system:basic-user → 所有已认证用户 基本信息 可查看自身身份
system:public-info-viewer → 所有用户(含未认证) 公开信息 可访问 /healthz/version 等端点

在你的集群中看看:

# 1. 列出所有由 K8s 系统自动创建的 ClusterRoleBinding(名字含 system: 或 cluster-admin)
kubectl get clusterrolebinding | grep -E '^NAME|cluster-admin|^system:'

典型输出(在任意 K8s 集群中都类似):

NAME                              ROLE                              AGE
cluster-admin                     ClusterRole/cluster-admin         30d
system:basic-user                 ClusterRole/system:basic-user     30d
system:discovery                  ClusterRole/system:discovery      30d
system:public-info-viewer         ClusterRole/system:public-info-viewer 30d
# 2. 看 cluster-admin 绑定的详情:绑给了谁?绑了什么角色?
kubectl describe clusterrolebinding cluster-admin

关键输出:

Name:         cluster-admin
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
Role:
  Kind:  ClusterRole
  Name:  cluster-admin
Subjects:
  Kind   Name            Namespace
  ----   ----            ---------
  Group  system:masters

X509 证书的 CNO 是怎么变成 K8s 用户名和组的?

X509 证书可以理解为一张数字身份证,里面有固定的信息字段。K8s 用其中两个字段来提取身份:

| 证书字段 | 全称 | 映射到 K8s | 示例 |

|---------|------|-----------|------|

| CN= | Common Name(常用名) | 用户名(User) | CN=zhangsan → User zhangsan |

| O= | Organization(组织) | 组名(Group) | O=developers → Group developers |

⚠️ O=大写英文字母 O(Organization 首字母),不是数字 0。敲命令时注意不要混淆。

一张证书可以有多个 O= 字段,对应 K8s 中的多个组。用 openssl 生成证书时这样写:

# 生成一个证书:用户名为 zhangsan,属于 developers 组和 ops 组
openssl req -new -key zhangsan.key -out zhangsan.csr \
  -subj "/CN=zhangsan/O=developers/O=ops"

K8s 收到请求后,从证书中提取出 CN=zhangsan → User zhangsan,提取出 O=developersO=ops → Group developers 和 Group ops。然后 RBAC 根据这些用户名和组名来匹配 RoleBinding 中的 subjects。

所以要理解上面的 cluster-admin 绑定: system:masters 正是一个内置组。任何证书中只要有一个 O=system:masters,证书持有者就自动获得 cluster-admin 的全部权限。这就是 Kubernetes 二进制部署实战 中生成 admin 证书时写 "/O=system:masters" 的原因——不需要另外创建 User 对象,证书本身就携带了「我是管理员」的身份。

# 3. 模拟匿名用户(未认证)能看到什么?
kubectl get --raw="/healthz"              # 公开端点,匿名用户可见
# ok

kubectl get --raw="/api"                  # API 列表,system:discovery 提供
# {"kind":"APIVersions","versions":["v1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0","serverAddress":"172.19.0.2:6443"}]}

kubectl get pods --as=system:anonymous    # 匿名用户没有 RBAC 权限 → 403
# Error from server (Forbidden): pods is forbidden: User "system:anonymous" cannot list resource "pods" in API group "" in the namespace "default"

# 4. 查看默认绑定都引用了哪些 ClusterRole
kubectl get clusterrole | grep -E '^NAME|cluster-admin|system:basic|system:discov|system:public'
输出:
NAME                                                                   CREATED AT
cluster-admin                                                          2026-07-30T02:55:54Z
system:basic-user                                                      2026-07-30T02:55:54Z
system:discovery                                                       2026-07-30T02:55:54Z
system:public-info-viewer                                              2026-07-30T02:55:54Z

这些内置 ClusterRole 由 kubernetes.io/bootstrapping=rbac-defaults 标签标记,K8s API Server 启动时自动创建并维护。不要手动删除它们——如果你误删了,只有重启 API Server 才能恢复。


4 授权 — RBAC 模型

前面搞清楚了「谁在操作」,现在来回答第二个问题:「他能做什么」。

RBAC(Role-Based Access Control)是 K8s 默认的授权模式,从 K8s v1.8 起稳定。它是目前最广泛使用的权限控制模型。

4.1 四个核心对象

Role / ClusterRole           → 定义"能做什么"
RoleBinding / ClusterRoleBinding  → 将 Role 绑定到 User / Group / ServiceAccount

这个结构可以这样理解:

  • Role = 岗位说明书("数据库管理员可以创建 Pod")
  • RoleBinding = 人事任命("让张三担任数据库管理员")

Role vs ClusterRole:

Role ClusterRole
作用域 单个 Namespace 整个集群
可授予资源 Namespace 内的资源 所有资源 + 集群级资源(Nodes、PV、CSR)
非资源 URL 不支持 支持(/healthz/api 等)

为什么要区分 Namespace 和集群? K8s 中的资源分为两类:Namespaced(命名空间级)(如 Pod、Service、ConfigMap)和 Cluster(集群级)(如 Node、PersistentVolume)。前者属于某个命名空间,后者属于整个集群。Role 只能管理 Namespaced 资源,ClusterRole 可以管理一切。

RoleBinding vs ClusterRoleBinding:

RoleBinding ClusterRoleBinding
作用域 仅限 Role 所在 Namespace 整个集群
可绑定 ClusterRole 在指定 Namespace 内生效 在全集群生效

一张图看清四个对象的关系:

        ┌──────────────┐
        │  你是谁?     │
        │  User/Group  │
        │  ServiceAcct │
        └──────┬───────┘
               │ subjects(被绑定者)
┌──────────────────────────────────────────────┐
│          RoleBinding / ClusterRoleBinding    │  ← 任命书:
│  subjects: [{kind: User, name: "zhangsan"}]  │    把谁(subjects)
│  roleRef:  {kind: Role, name: "pod-reader"}  │    绑定到什么角色
└──────────────┬───────────────────────────────┘
               │ roleRef(引用角色)
┌──────────────────────────────────────┐
│     Role / ClusterRole               │  ← 岗位说明书:能做什么
│  rules:                              │
│  - apiGroups: [""]                   │
│    resources: ["pods"]               │
│    verbs: ["get", "list", "watch"]   │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│     API Server 收到 kubectl 请求      │
│  → 提取用户身份(认证阶段)              │
│  → 查该用户被绑定到的所有 Role           │
│  → 检查请求的 verb+resource 是否匹配    │
│  → 允许(200)或拒绝(403)             │
└──────────────────────────────────────┘

初学者理解要点: Role 和 RoleBinding 是两个独立的资源,就像「岗位说明书」和「人事任命」是两份独立的文档。创建 Role 只是定义了某个权限规则,但没有人被授予这个权限——必须再创建 RoleBinding 把用户/ServiceAccount 跟 Role 关联起来,权限才会生效。

另注意区分两个概念:YAML 第一行的 apiVersion 声明「我是什么资源」,而 Role rules 里的 apiGroups 定义「我能管哪些资源」——两者虽共用 API 组机制,但作用完全不同。apiVersion 的详细介绍见 Kubernetes Pod 与 Deployment 实战

4.2 Role 示例

以下 Role 在 vertica 命名空间中授予了对 Pod、Service、ConfigMap 和 Deployment 的完整管理权限。apiGroups 指定 API 组,resources 指定对象类型,verbs 指定可执行的操作。

# Namespace 作用域的 Role — 权限只在 vertica 这个 namespace 内有效
apiVersion: rbac.authorization.k8s.io/v1
kind: Role                                                                 # Role = Namespace 级别
metadata:
  namespace: vertica                                                       # 限定作用域:只对该 namespace 的资源有效
  name: vertica-admin
rules:
# 规则 1:核心 API 组(空字符串 = "")— Pod、Service、ConfigMap 等基础资源
- apiGroups: [""]                                                          # 空字符串代表核心(core)API 组
  resources: ["pods", "services", "configmaps", "secrets", "pods/log"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]   # 7 个常用 verb(不含 deletecollection):允许几乎所有操作,包括删除
# 规则 2:apps API 组 — StatefulSet、Deployment 等工作负载资源
- apiGroups: ["apps"]                                                      # apps 组包含 Deployment、StatefulSet、DaemonSet 等
  resources: ["statefulsets", "deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]   # 同样全量权限
# 规则 3:batch API 组 — Job、CronJob 等批处理资源,只允许创建不允许删除
- apiGroups: ["batch"]                                                     # batch 组包含 Job、CronJob
  resources: ["jobs", "cronjobs"]
  verbs: ["get", "list", "watch", "create"]                                # 少给了 delete 和 patch:Job 可以创建但不能删

4.3 ClusterRole 示例

# ========== ClusterRole:集群级权限定义 ==========
# 没有 namespace 字段 — 因为 ClusterRole 作用于整个集群
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole                                           # ClusterRole = 集群级别
metadata:
  name: cluster-reader                                      # 集群内唯一名称(不限定 namespace)
rules:
# 规则 1:集群级资源 — Node、PV 等不属于任何 namespace 的资源
- apiGroups: [""]
  resources: ["nodes", "persistentvolumes", "namespaces"]   # Node/PV 是集群级资源,Role 无法管理,必须用 ClusterRole 才能授权
  verbs: ["get", "list", "watch"]                           # 只读权限
# 规则 2:非资源端点 — ClusterRole 独有的能力
- nonResourceURLs: ["/healthz", "/livez", "/readyz"]        # 非资源 URL(不以 /api/ 开头),Role 不支持 nonResourceURLs,这是 ClusterRole 的独有特性
  verbs: ["get"]                                            # 对健康检查端点只有 GET
---
# ========== ClusterRoleBinding:将 ClusterRole 授予用户 ==========
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding                                    # ClusterRoleBinding = 全集群范围生效
metadata:
  name: cluster-readers
subjects:                                                   # subjects = 被授权者
- kind: User                                                # 可以是 User、Group、ServiceAccount
  name: simon
  apiGroup: rbac.authorization.k8s.io
roleRef:                                                    # roleRef = 引用上面定义的 ClusterRole
  kind: ClusterRole                                         # 必须是 ClusterRole(不能绑定普通 Role)
  name: cluster-reader                                      # 指向上面创建的 ClusterRole
  apiGroup: rbac.authorization.k8s.io

重要: RoleBinding 和 ClusterRoleBinding 中的 roleRef 字段是不可变的(immutable)。一旦创建绑定,不能通过直接修改 roleRef 来更换关联的角色 — 必须删除绑定后重建。这是一种安全设计,防止权限被意外或恶意篡改。

重要: RoleBinding 只能引用同一 Namespace 中的 Role,引用错误的 Namespace 会导致绑定失败。

4.4 内置 ClusterRole

K8s 提供了四个面向用户的内置 ClusterRole,在生产环境中非常实用:

内置 ClusterRole 权限范围 关键限制
cluster-admin 所有资源(含集群范围和非资源端点)的完全控制 默认绑定至 system:masters
admin Namespace 内完整管理权 不能修改 ResourceQuota 和 Namespace 对象本身
edit Namespace 内读写大部分资源 不能查看或修改 RBAC 资源(Role/RoleBinding),可读取 Secret
view Namespace 内只读 不能读取 Secret 和 RBAC 资源,不能查看 Pod 日志

使用场景: 给开发者 edit 权限(可部署但不可改 RBAC),给非敏感岗位 view 权限(查看但不可修改和看机密),运维团队用 admin(管理整个 Namespace),集群管理员用 cluster-admin

4.5 RBAC Verbs 详解

Verbs 是什么意思? "Verb" 是英文语法中的「动词」。K8s 借用了 REST API 的核心隐喻:

  • resources(资源)= 名词:Pod、Service、Deployment……这些都是「什么东西」。
  • verbs(动作)= 动词:get、create、delete……这些都是「对这个东西做什么」。

这个隐喻来自 HTTP 协议本身——HTTP 方法(GET、POST、PUT、DELETE)在 REST 架构中也被称为「HTTP verbs」。K8s API Server 本身就是一个 RESTful API,所以用 verbs 来描述操作是最自然的。换句话说:kubectl get pods 中的 getpods 正好就是「动词 + 名词」。

Verb HTTP 方法 说明
get GET 获取单个资源
list GET (collection) 获取资源列表
watch GET (watch) 监听资源变更
create POST 创建资源
update PUT 全量更新资源
patch PATCH 部分更新资源
delete DELETE 删除单个资源
deletecollection DELETE (collection) 删除所有匹配的资源

常用权限组合:

# 只读访问
verbs: ["get", "list", "watch"]

# 完整管理
verbs: ["*"]

# 除删除外的管理
verbs: ["get", "list", "watch", "create", "update", "patch"]

4.6 使用 kubectl 管理 RBAC

除了写 YAML 文件,kubectl 也提供了命令行快速创建 RBAC 资源的方式,适合测试和脚本编排。以下命令在 k8s-playground 集群中直接可用:

===== 准备 namespace =====
# 前面只有 default namespace
kubectl create namespace dev
# namespace/dev created


===== 创建 Role + RoleBinding =====
# 在 dev namespace 创建只读 Pod 的角色
kubectl create role pod-reader --verb=get,list,watch --resource=pods -n dev
# role.rbac.authorization.k8s.io/pod-reader created

# 把 pod-reader 角色绑给用户 simon
kubectl create rolebinding simon-pod-reader --role=pod-reader --user=simon -n dev
# rolebinding.rbac.authorization.k8s.io/simon-pod-reader created


===== 创建 ClusterRole + ClusterRoleBinding =====
# 集群级别:只读 Node 信息
kubectl create clusterrole node-reader --verb=get,list,watch --resource=nodes
# clusterrole.rbac.authorization.k8s.io/node-reader created

# 把 node-reader 集群角色绑给用户 simon(全集群生效)
kubectl create clusterrolebinding simon-node-reader --clusterrole=node-reader --user=simon
# clusterrolebinding.rbac.authorization.k8s.io/simon-node-reader created


===== 验证权限 =====
# simon 在 dev namespace 能 list Pod 吗?(上面绑了 pod-reader → yes)
kubectl auth can-i list pods --as=simon -n dev
# yes

# simon 能删 Pod 吗?(pod-reader 只给了 get/list/watch → no)
kubectl auth can-i delete pods --as=simon -n dev
# no

# simon 能跨 namespace 吗?(Role 是 namespace 级别,上面只给了 dev namespace 的权限,没有 default 权限 → no)
kubectl auth can-i list pods --as=simon -n default
# no

# simon 能 list Node 吗?(上面给了 node-reader ClusterRole → yes)
kubectl auth can-i list nodes --as=simon
# Warning: resource 'nodes' is not namespace scoped   # Warning 只是 kubectl 友好提醒:nodes 不属于任何 namespace,-n 参数对它无效——这是集群级资源的正常行为。
# yes

===== 排查绑定 =====
# 全局扫描:哪些 RoleBinding / ClusterRoleBinding 提到了 simon?
kubectl get rolebindings,clusterrolebindings -A | grep simon
# NAMESPACE   NAME                                                              ROLE                       AGE
# dev         rolebinding.rbac.authorization.k8s.io/simon-pod-reader            Role/pod-reader            3m5s
#             clusterrolebinding.rbac.authorization.k8s.io/simon-node-reader    ClusterRole/node-reader    119s

# 查看某个 Role 的详细规则
kubectl describe role pod-reader -n dev
# Name:         pod-reader
# Labels:       <none>
# Annotations:  <none>
# PolicyRule:
#   Resources  Non-Resource URLs  Resource Names  Verbs
#   ---------  -----------------  --------------  -----
#   pods       []                 []              [get list watch]

上面每条命令的等价 YAML:

# rbac.yaml
# ===== Role:对应 kubectl create role pod-reader =====
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
# ===== RoleBinding:对应 kubectl create rolebinding simon-pod-reader =====
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev
  name: simon-pod-reader
subjects:
- kind: User
  name: simon
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
---
# ===== ClusterRole:对应 kubectl create clusterrole node-reader =====
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list", "watch"]
---
# ===== ClusterRoleBinding:对应 kubectl create clusterrolebinding simon-node-reader =====
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: simon-node-reader
subjects:
- kind: User
  name: simon
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: node-reader
  apiGroup: rbac.authorization.k8s.io

kubectl create role/rolebinding 适合快速原型和调试。生产环境推荐用 YAML 文件管理:kubectl apply -f rbac.yaml,方便版本控制和审计。

4.7 跟着做:十分钟完成一套 RBAC 配置

本教程沿用系列统一的实验环境:Kind 集群 k8s-playground。如果你还没有搭好,参考「Kubernetes Pod 与 Deployment 实战」。

场景: 你有一个 dev namespace,需要给同事小明(xiaoming)分配只读权限,给小红(xiaohong)分配读写权限但不允许删东西

# 0. 准备工作:创建 namespace 并切换到该上下文
kubectl create namespace dev
kubectl config set-context --current --namespace=dev
kubectl config view | grep namespace    # 检查当前上下文
# namespace: dev

# 1. 创建只读 Role
kubectl create role readonly \
  --verb=get,list,watch \
  --resource=pods,services,configmaps,deployments \
  -n dev
# role.rbac.authorization.k8s.io/readonly created

# 2. 创建读写但不能删的 Role
kubectl create role readwrite-nodelete \
  --verb=get,list,watch,create,update,patch \
  --resource=pods,services,configmaps,deployments \
  -n dev
# role.rbac.authorization.k8s.io/readwrite-nodelete

# 3. 绑定小明 → 只读
kubectl create rolebinding xiaoming-readonly \
  --role=readonly --user=xiaoming -n dev
# rolebinding.rbac.authorization.k8s.io/xiaoming-readonly created

# 4. 绑定小红 → 读写不能删
kubectl create rolebinding xiaohong-readwrite \
  --role=readwrite-nodelete --user=xiaohong -n dev
# rolebinding.rbac.authorization.k8s.io/xiaohong-readwrite created

# 5. 验证小明的权限
kubectl auth can-i get pods --as=xiaoming -n dev         # yes
kubectl auth can-i delete pods --as=xiaoming -n dev       # no(只给了 get/list/watch)
kubectl auth can-i delete pod/test --as=xiaoming -n dev   # no(只给了 get/list/watch)
kubectl auth can-i get pods --as=xiaoming -n default      # no ← 跨 namespace 无效!

# 6. 验证小红的权限
kubectl auth can-i create deployment/nginx --as=xiaohong -n dev  # yes(创建 nginx deployment)
kubectl auth can-i delete deployment/nginx --as=xiaohong -n dev  # no(删除 nginx deployment)

关键要点: - 小明的只读权限只在 dev namespace 生效,换个 namespace 就失效——这说明 Role 是 Namespace 级别的。 - 小红可以创建但不能删除——这说明 verb 级别的精细化控制是可行的。 - 如果你想让某人跨所有 namespace 都有只读权限,应该用 ClusterRole + ClusterRoleBinding。

为什么 CLI 不用手动分 apiGroups? 上面的命令行里 --resource=pods,services,configmaps,deployments 混着写也没报错——因为 kubectl 知道每个资源属于哪个 API 组(Pod→"", Deployment→apps),帮你自动拆了。用 kubectl describe role readonly -n dev 可以看到内部已经是两条规则。写 YAML 时则必须自己拆开,因为 YAML 是声明式的,K8s 不会帮你推断。

把 CLI 建的 Role 导出为 YAML:

# kubectl get -o yaml 可以导出任何已创建的资源
kubectl get role readonly -n dev -o yaml

输出会包含 metadata.uidmetadata.creationTimestamp 等运行时字段。最实用的方式是用 kubectl describe 看规则结构,然后手写干净的 YAML:

# describe 输出比 -o yaml 更易读,规则部分可以直接参考
kubectl describe role readonly -n dev
# 输出:
# Name:         readonly
# Labels:       <none>
# Annotations:  <none>
# PolicyRule:
#   Resources         Non-Resource URLs  Resource Names  Verbs
#   ---------         -----------------  --------------  -----
#   configmaps        []                 []              [get list watch]
#   pods              []                 []              [get list watch]
#   services          []                 []              [get list watch]
#   deployments.apps  []                 []              [get list watch]

实际工作流:用 CLI 快速试验 → kubectl describe 确认规则正确 → 参照输出手写 YAML → kubectl apply -f 纳入版本控制。RBAC 的 YAML 很简单,不值得为了导出而安装额外工具。

上面每条命令的等价 YAML(推荐生产环境用 kubectl apply -f 管理):

# dev-rbac.yaml
# ===== Role:对应 kubectl create role readonly =====
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: readonly
rules:
- apiGroups: [""]                     # 核心 API 组:Pod、Service、ConfigMap 属于这里
  resources: ["pods", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]                 # apps API 组:Deployment、StatefulSet 属于这里
  resources: ["deployments"]          # 不能和 Pod 写在一起——K8s 按 API 组严格隔离资源
  verbs: ["get", "list", "watch"]
---
# ===== Role:对应 kubectl create role readwrite-nodelete =====
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: readwrite-nodelete
rules:
- apiGroups: [""]                     # 核心 API 组
  resources: ["pods", "services", "configmaps"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["apps"]                 # apps API 组:Deployment 必须写在这里,不能和 Pod 混
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]
---
# ===== RoleBinding:对应 kubectl create rolebinding xiaoming-readonly =====
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev
  name: xiaoming-readonly
subjects:
- kind: User
  name: xiaoming
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: readonly
  apiGroup: rbac.authorization.k8s.io
---
# ===== RoleBinding:对应 kubectl create rolebinding xiaohong-readwrite =====
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev
  name: xiaohong-readwrite
subjects:
- kind: User
  name: xiaohong
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: readwrite-nodelete
  apiGroup: rbac.authorization.k8s.io

kubectl create role/rolebinding 快速原型验证后,把上面的 YAML 保存为 dev-rbac.yaml,执行 kubectl apply -f dev-rbac.yaml 即可纳入版本控制。后续修改权限时改 YAML 再 apply,而不是用 CLI 重建。

查看绑定结果:

# 看看 dev namespace 下有哪些 Role 和 RoleBinding
kubectl get role,rolebinding -n dev
k8s@debian:~$ kubectl get role,rolebinding -n dev
# NAME                                                CREATED AT
# role.rbac.authorization.k8s.io/pod-reader           2026-07-31T02:59:30Z
# role.rbac.authorization.k8s.io/readonly             2026-07-31T03:28:01Z
# role.rbac.authorization.k8s.io/readwrite-nodelete   2026-07-31T03:28:26Z

# NAME                                                       ROLE                      AGE
# rolebinding.rbac.authorization.k8s.io/simon-pod-reader     Role/pod-reader           49m
# rolebinding.rbac.authorization.k8s.io/xiaohong-readwrite   Role/readwrite-nodelete   20m
# rolebinding.rbac.authorization.k8s.io/xiaoming-readonly    Role/readonly             21m


# 用 describe 看具体规则
kubectl describe role readonly -n dev
# Name:         readonly
# Labels:       <none>
# Annotations:  <none>
# PolicyRule:
#   Resources         Non-Resource URLs  Resource Names  Verbs
#   ---------         -----------------  --------------  -----
#   configmaps        []                 []              [get list watch]
#   pods              []                 []              [get list watch]
#   services          []                 []              [get list watch]
#   deployments.apps  []                 []              [get list watch]

kubectl describe rolebinding xiaoming-readonly -n dev
# Name:         xiaoming-readonly
# Labels:       <none>
# Annotations:  <none>
# Role:
#   Kind:  Role
#   Name:  readonly
# Subjects:
#   Kind  Name      Namespace
#   ----  ----      ---------
#   User  xiaoming

常见翻车现场: 创建了 Role 但忘记创建 RoleBinding?权限不会生效,回到上面检查。RoleBinding 引用了别的 namespace 的 Role?绑定失败,RoleBinding 只能引用同一 namespace 的 Role。


5 ServiceAccount — Pod 级身份

前面 RBAC 例子里的 xiaomingsimon 都只是字符串——它们是「人」的身份。但 Pod 呢? Pod 里面的程序也需要调用 API Server——监控 agent 要读 Pod 列表,CI/CD runner 要创建 Deployment。 ServiceAccount 就是给 Pod 办的「机器工牌」:它是 K8s 中唯一真正存在的一等公民身份,Operator、CronJob、CI/CD 系统都通过它操作 K8s API。

下面用 k8s-playground 中的 dev namespace 做一个可直接复用的示例:

# ===== ServiceAccount:在 dev namespace 创建一个「机器身份」 =====
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bot-reader            # SA 名称
  namespace: dev              # 与后续 RoleBinding 在同一 namespace
---
# ===== Role:定义这个 SA 能做啥 =====
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader            # 复用前面 4.6 节已创建的 Role
  namespace: dev
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
# ===== RoleBinding:把 SA 绑定到 Role =====
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: bot-reader-binding
  namespace: dev
subjects:
- kind: ServiceAccount        # subjects 类型换成了 ServiceAccount
  name: bot-reader
  namespace: dev              # SA 所在的 namespace
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

关键点: ServiceAccount 的 Token 会自动挂载到 Pod 中,但可以通过 automountServiceAccountToken: false 关闭。subjects 中的 kind: ServiceAccount 是 YAML 层面区分三种身份主体(User / Group / ServiceAccount)的方式(命令行中则靠 system:serviceaccount: 前缀)——User 只是字符串,ServiceAccount 是真实存在的一等公民。

动手验证:创建一个 ServiceAccount 并测试其权限

前面 4.7 节小明的权限是通过 --as=xiaoming 模拟的(xiaoming 只是字符串,不是真实 K8s 对象)。ServiceAccount 不同——它是真正存在于集群中的「机器身份」。下面在 k8s-playground 中走一遍完整流程:

# 1. 创建 ServiceAccount
kubectl create serviceaccount bot-reader -n dev
# serviceaccount/bot-reader created

# 2. 赋予它只读 Pod 的权限(用前面 4.6 节已有的 pod-reader role)
kubectl create rolebinding bot-reader-binding --role=pod-reader --serviceaccount=dev:bot-reader -n dev
# rolebinding.rbac.authorization.k8s.io/bot-reader-binding created

# 3. 替这个 SA 问:它能 list Pod 吗?
kubectl auth can-i list pods --as=system:serviceaccount:dev:bot-reader -n dev
# yes

# 4. 它能删 Pod 吗?(pod-reader 只给 get/list/watch)
kubectl auth can-i delete pods --as=system:serviceaccount:dev:bot-reader -n dev
# no

SA 全限定名格式: system:serviceaccount:<namespace>:<sa-name>。如果只写 --as=bot-reader,K8s 会把它当成一个叫 bot-reader 的普通 User 而不是 ServiceAccount——前缀 system:serviceaccount: 就是区分两者的标识。

在 Pod 中使用 ServiceAccount:

ServiceAccount 最终是要被 Pod 使用的——这是它和普通 User 最大的区别。下面创建一个 Pod 挂载 bot-reader SA:

# 5. 创建 Pod,指定 SA(和 02 篇 Pod YAML 唯一的区别就是 spec 里多了一行 serviceAccountName)
kubectl apply -f - << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: bot-test
  namespace: dev
spec:
  serviceAccountName: bot-reader   # 指定 SA,Pod 启动后 Token 自动挂载
  containers:
  - name: nginx
    image: nginx:1.25              # 02 篇已拉取过的镜像,无需重新下载
EOF

# 6. 进容器看一眼 —— Token 已经自动挂载好了
kubectl exec bot-test -n dev -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
# (一段 JWT Token)
# eyJhbGciOiJSUzI1NiIsImtpZCI6IlUxRWNXTWplQ001T0dhbVRCcUphUC1zeGdCZm9tMUNSVW5hamFxVy1uU2cifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxODE3MDIxMzU2LCJpYXQiOjE3ODU0ODUzNTYsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiNzZjN2Y2OTUtOTg0Zi00MTg5LTkxMWItYjExMjcyMWYwMTBmIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZXYiLCJub2RlIjp7Im5hbWUiOiJrOHMtcGxheWdyb3VuZC1jb250cm9sLXBsYW5lIiwidWlkIjoiODY0NWJiZDEtZTljNC00ZWI4LWJlYjYtMDEyOWMyMjI2YThiIn0sInBvZCI6eyJuYW1lIjoiYm90LXRlc3QiLCJ1aWQiOiJmN2E5NzMxMC01N2Q0LTQ1NzktOTA4Mi0wMDc3YTIzMDcwMjEifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImJvdC1yZWFkZXIiLCJ1aWQiOiIyM2Y0ODRhYy1jNTk4LTRmNzQtOWE4Ny1hZmU5ZDQzNzlkOTEifSwid2FybmFmdGVyIjoxNzg1NDg4OTYzfSwibmJmIjoxNzg1NDg1MzU2LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGV2OmJvdC1yZWFkZXIifQ.TaG6waQqazC3uU_zBarh2RsR3BNKGWh8jwhkaLrIwhxSOfzUBVPrgjj31ZOFdrJLR8ChmTHV6VpjyjcY5ULjf4G7QD7-6T_zyXPjbAdmMv5V0dCFnHHWsjZURQkf5l-MNZhnmDs8mIm66IcYRmoq6Pyb2DY1zBLAlMX4SYP1v7vlE-Yh5qJlv9akaYUmHPR14CfHCfGqeMKjO17NrhfP-1xpDfOn65wxmPkcsYsPMS3H6AEgfQ9F_iPrgdy9k8aYDMY7ya7H9bOxfnslq8Ntz_TSVmqgEao3AnN8fXC-xWuiv4HXw3M_A6gyOM3f78yqe5CgblscaMMBPQvHCpPkeg

# 检查 TOKEN 的 sub 字段
kubectl exec bot-test -n dev -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool | grep '"sub"'
# "sub": "system:serviceaccount:dev:bot-reader"


# 7. 验证 Pod 的权限:两个 Token 的 jti/iat/exp 不同,但 sub 字段都是
#    system:serviceaccount:dev:bot-reader —— 身份一致,权限就一致
TOKEN=$(kubectl create token bot-reader -n dev)
echo $TOKEN
# eyJhbGciOiJSUzI1NiIsImtpZCI6IlUxRWNXTWplQ001T0dhbVRCcUphUC1zeGdCZm9tMUNSVW5hamFxVy1uU2cifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzg1NDg5MDMyLCJpYXQiOjE3ODU0ODU0MzIsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiYzc3NzhiMzgtZWViMy00NThkLTg5YTQtM2ZmYjkzZmE4M2YzIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZXYiLCJzZXJ2aWNlYWNjb3VudCI6eyJuYW1lIjoiYm90LXJlYWRlciIsInVpZCI6IjIzZjQ4NGFjLWM1OTgtNGY3NC05YTg3LWFmZTlkNDM3OWQ5MSJ9fSwibmJmIjoxNzg1NDg1NDMyLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGV2OmJvdC1yZWFkZXIifQ.C-xJZ4sslY-3GNw68iw6pME8NRkTAY7_VvstKamKius8toWbUMGXVTa38xfP5jx3p9bsRgFbT-HdhmuLcDLY5sPqoqa39c-W4MbUzba6drK-tgrN8tH3nMloTYDKtmsRVCnfa5q5lSdJrfICacSH--MBC3EKrPtQpi3o-Ab2RWOcUE3nXQBuwgQRhAiCiuWKkdSdXokQV24bfDp3_nhIf9VaL1XvGJzrhkxQOTd1H2z7YoUOKmcDErX9rDqLIvhp_02RTdxsbwbfutAwX8n-PaQrUUYWouyuZWW7VeCGIalx3EX3eHaljywdFWZRob_gBtMpnpw_w-Vz_14xP1pF4Q

# 检查 TOKEN 的 sub 字段
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool | grep '"sub"'
# "sub": "system:serviceaccount:dev:bot-reader"

# 用 curl 直接调 API(不用 kubectl --token=,因为它会回退到 kubeconfig 的 cluster-admin 证书)
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
curl -sk -H "Authorization: Bearer $TOKEN" $APISERVER/api/v1/namespaces/dev/pods
# 返回 200 + Pod 列表 — Token 被正确识别为 bot-reader SA,能 list Pod

# 同样 Token,换 POST —— RBAC 生效!
curl -sk -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" $APISERVER/api/v1/namespaces/dev/pods \
  -d '{"apiVersion":"v1","kind":"Pod","metadata":{"name":"curl-test"},"spec":{"containers":[{"name":"nginx","image":"nginx:1.25"}]}}'
# {
#   "kind": "Status",
#   "apiVersion": "v1",
#   "metadata": {},
#   "status": "Failure",
#   "message": "pods is forbidden: User \"system:serviceaccount:dev:bot-reader\" cannot create resource \"pods\" in API group \"\" in the namespace \"dev\"",
#   "reason": "Forbidden",
#   "details": {
#     "kind": "pods"
#   },
#   "code": 403
# }

kubectl auth can-i create pods --as=system:serviceaccount:dev:bot-reader -n dev
# no — 只有 get/list/watch,没有 create

⚠️ 这里用 curl 而非 kubectl --token= 是有原因的:kubectl 在有 client-certificate 的环境中会回退到证书认证,导致 --token 被忽略。curl 只发送你指定的 Token,不做任何回退,结果才是准确的。kubectl auth can-i --as= 也同样可靠——它直接调用 SubjectAccessReview API。

以上演示了 SA → Token → API Server 的完整链路。Pod 里的 Token 和 kubectl create token 生成的 Token 是两个不同的 JWT(jtiiatexp 都不一样),但它们的 sub 字段完全相同——都是 system:serviceaccount:dev:bot-reader。RBAC 不看 Token 是不是同一个,只看 sub 是谁,然后匹配对应的 Role。

# 清理(bot-test Pod 后面 PSA 和 X509 附录实验还要用,先留着)
kubectl delete serviceaccount bot-reader -n dev
kubectl delete rolebinding bot-reader-binding -n dev

5.1 RBAC 调试三板斧

配置完 RBAC 之后,最常见的情况是:「为啥我权限不够?」别慌,三步定位问题:

第一板斧:kubectl auth can-i — 问系统我能不能做

# 「我能删 dev namespace 里的 Pod 吗?」
kubectl auth can-i delete pods -n dev
# yes

# 我替小明问:「小明能列出 dev 里的所有 Pod 吗?」
kubectl auth can-i list pods --as=xiaoming -n dev
# yes

# 替 ServiceAccount 问:「bot-reader 这个 SA 能删 Pod 吗?」
kubectl auth can-i delete pods --as=system:serviceaccount:dev:bot-reader -n dev
# no

auth can-i 是最可靠的方式——它完整走了一遍 API Server 的鉴权流程,结果跟你实际操作时一致。

第二板斧:kubectl describe — 检查绑定有没有配错

# 查看某个 Role 的具体规则
kubectl describe role pod-reader -n dev
# Name:         pod-reader
# Labels:       <none>
# Annotations:  <none>
# PolicyRule:
#   Resources  Non-Resource URLs  Resource Names  Verbs
#   ---------  -----------------  --------------  -----
#   pods       []                 []              [get list watch]


# 查看某个 RoleBinding 绑了谁、绑了什么 Role
kubectl describe rolebinding xiaoming-readonly -n dev
# Name:         xiaoming-readonly
# Labels:       <none>
# Annotations:  <none>
# Role:
#   Kind:  Role
#   Name:  readonly
# Subjects:
#   Kind  Name      Namespace
#   ----  ----      ---------
#   User  xiaoming

# 把所有 RoleBinding 列出来,看有没有遗漏
kubectl get rolebinding -n dev -o wide
# NAME                 ROLE                      AGE     USERS      GROUPS   SERVICEACCOUNTS
# bot-reader-binding   Role/pod-reader           3h46m                       dev/bot-reader
# simon-pod-reader     Role/pod-reader           5h45m   simon
# xiaohong-readwrite   Role/readwrite-nodelete   5h16m   xiaohong
# xiaoming-readonly    Role/readonly             5h16m   xiaoming

常见问题:describe 出来的 roleRef 引用了不存在的 Role、或者 subjects 里的用户名拼错了。

第三板斧:kubectl get 全量排查 — 看看全局有没有遗漏

# 全集群扫描:哪些 ClusterRoleBinding 绑了 xiaoming?
kubectl get clusterrolebinding -A -o yaml | grep -A5 xiaoming

# 扫描所有 namespace:xiaoming 在哪些地方有绑定?
kubectl get rolebinding -A | grep xiaoming
# NAMESPACE     NAME                ROLE            AGE
# dev           xiaoming-readonly   Role/readonly   5h20m

调试口诀: 遇到 403 → 先用 auth can-i 确认是权限问题 → 再用 describe 检查绑定配置 → 最后 get -A 全量扫描确保没有跨 namespace 遗漏。


6 Pod Security Admission(PSA)

RBAC 管的是「谁能操作什么资源」,但如果有权限的人故意或不小心创建了一个 privileged: true 的 Pod 呢?这个 Pod 可以逃逸容器隔离,访问宿主机的内核和硬件。PSA 的作用就是限制 Pod 自己能有多「危险」——无论谁创建的,不合规的 Pod 直接拒绝。

PSA 是 K8s v1.23 引入的 Pod 安全标准,替代废弃的 PodSecurityPolicy。它定义了三个安全等级:

等级 说明 典型限制
Privileged 无限制 无(兼容特权容器)
Baseline 最小限制,能防已知特权提升 禁止 hostNetwork、hostPID、hostIPC、privileged 容器
Restricted 最严格,遵循 Pod 加固最佳实践 强制 non-root、只读根文件系统、禁止特权提升

6.1 配置方式

PSA 通过 Namespace 标签控制,共 6 个标签:

标签 作用 可选值
pod-security.kubernetes.io/enforce 强制拒绝不合规 Pod privileged / baseline / restricted
pod-security.kubernetes.io/audit 仅记录审计日志,不拦截 同上
pod-security.kubernetes.io/warn 返回警告但允许创建 同上
.../enforce-version enforce 对应的 K8s 版本 latestv1.27
.../audit-version audit 对应的 K8s 版本 同上
.../warn-version warn 对应的 K8s 版本 同上

三个级别各自禁止了什么:

限制项 Privileged Baseline Restricted
privileged 容器
hostNetwork/hostPID/hostIPC
hostPath 卷 / hostPort
allowPrivilegeEscalation=false ❌ 必填
runAsNonRoot=true ❌ 必填
seccompProfile=RuntimeDefault ❌ 必填
capabilities.drop=["ALL"] ❌ 必填

✅=允许 ❌=禁止 —=不限制。推荐策略: 先用 warn=baseline 审计,确认无影响后升级为 enforce=baseline

# Namespace 级别配置
apiVersion: v1
kind: Namespace
metadata:
  name: vertica-prod
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

动手实验:给 dev namespace 加上 PSA,看看效果

# 1. 查看当前 namespace 有没有 PSA 标签(Kind 默认没有)
kubectl get ns dev --show-labels
# NAME   STATUS   AGE     LABELS
# dev    Active   6h57m   kubernetes.io/metadata.name=dev

# 2. 给 dev 加上 enforce=restricted(最严格级别)
kubectl label ns dev pod-security.kubernetes.io/enforce=restricted --overwrite
# Warning: existing pods in namespace "dev" violate the new PodSecurity enforce level "restricted:latest"
# Warning: bot-test: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile
# namespace/dev labeled

# Warning 不阻止 label — bot-test 违反了 restricted 级别(因为 nginx 镜像默认以 root 运行),但 PSA 只管新建 Pod,已存在的 Pod 不受影响

kubectl get ns dev --show-labels
# NAME   STATUS   AGE     LABELS
# dev    Active   6h58m   kubernetes.io/metadata.name=dev,pod-security.kubernetes.io/enforce=restricted


# 3. 试着创建一个有特权的 Pod —— 应该被拒绝
kubectl apply -f - << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: privileged-test
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.25
    securityContext:
      privileged: true         # restricted 禁止特权容器
EOF

# Error from server (Forbidden): error when creating "STDIN": pods "privileged-test" is forbidden: violates PodSecurity "restricted:latest": privileged (container "nginx" must not set securityContext.privileged=true), allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")

# 4. 同样的 Pod 在 default namespace(没有 PSA 限制)就可以
kubectl apply -f - << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: privileged-test
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx:1.25
    securityContext:
      privileged: true
EOF
# pod/privileged-test created — default namespace 没有 PSA 限制

# 5. 把 dev 的 enforce 降到 baseline
kubectl label ns dev pod-security.kubernetes.io/enforce=baseline --overwrite
# namespace/dev labeled

kubectl get ns dev --show-labels
# NAME   STATUS   AGE    LABELS
# dev    Active   7h9m   kubernetes.io/metadata.name=dev,pod-security.kubernetes.io/enforce=baseline

enforce / audit / warn 的区别: enforce 是硬拒绝,audit 只记录审计日志不拦截,warn 给用户返回警告但允许创建。生产环境建议先用 warn 观测,确认无误后再加 enforce

VerticaDB Operator 场景: Vertica Pod 需要 hostNetwork(用于集群间通信),无法满足 restricted 级别。因此 Vertica 的 Namespace 通常使用 baseline

# 清理
kubectl delete pod privileged-test -n default
kubectl label ns dev pod-security.kubernetes.io/enforce-  # 恢复默认
# namespace/dev unlabeled

kubectl get ns dev --show-labels
# NAME   STATUS   AGE     LABELS
# dev    Active   6h57m   kubernetes.io/metadata.name=dev

7 Secret 加密(Encryption at Rest)

RBAC 控制的是「谁」能读写 Secret,但它解决不了一个更底层的问题:如果攻击者直接拿到了 etcd 的磁盘数据怎么办? etcd 存储了整个集群的全部状态——所有的 Secret、ConfigMap、Pod 定义——而 Secret 的 data 字段在里面是明文存储的(base64 不算加密,任何人都能解码)。Encryption at Rest 就是在 API Server 写入 etcd 之前先用 AES-CBC 加密,即使有人偷走 etcd 的数据文件,没有加密密钥也解不出 Secret 内容。

⚠️ 首先纠正一个常见误解:Secret 的 data 字段是 base64 编码,不是加密。base64 只是把二进制转成可打印字符,任何人拿到字符串都能 echo xxx | base64 -d 还原原文——和明文没区别。

动手看看:

# 1. 创建一个 Secret,看看 data 字段长什么样
kubectl create secret generic db-password -n dev --from-literal=password=MySecret123

kubectl get secret db-password -n dev -o yaml
# data:
#   password: TXlTZWNyZXQxMjM=     ← 不是加密,是 base64 编码!

echo "TXlTZWNyZXQxMjM=" | base64 -d   # → MySecret123,任何人都能还原

# 2. Secret 在 etcd 中也是明文(base64 decoded = 原文)
#    用 kubectl raw API 可以直接看到
kubectl get --raw="/api/v1/namespaces/dev/secrets/db-password" | python3 -m json.tool | grep '"password"'
# "password": "TXlTZWNyZXQxMjM="

kubectl delete secret db-password -n dev

所以需要 Encryption at Rest: 本质是让 API Server 在写入 etcd 之前用 AES-CBC 加密 data 字段,读取时自动解密。配置方式是在 API Server 启动参数中指定 --encryption-provider-config 指向一个 EncryptionConfiguration 文件:

# /etc/kubernetes/encryption-config.yaml(需在 API Server 节点上创建)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:                    # AES-CBC 加密
      keys:
      - name: key1
        secret: <base64-encoded-32-byte-key>
  - identity: {}               # 兼容历史明文数据;新增 Secret 继续以明文写入

Kind 环境中无法演示 — EncryptionConfiguration 需要修改 API Server 的静态 Pod 定义(/etc/kubernetes/manifests/kube-apiserver.yaml),这属于集群初始化配置,不在运行时可见。配置生效后,kubectl get secret -o yaml 看到的数据依然是 base64 格式(解密由 API Server 透明完成),只有直接读 etcd 才能看到密文。

生产环境检查加密是否生效:

# 查看 API Server 是否加载了加密配置(最可靠的方式)
kubectl -n kube-system get pod -l component=kube-apiserver -o yaml \
  | grep encryption-provider-config
# 如果输出 --encryption-provider-config=/path/to/encryption-config.yaml 说明已启用
kubectl get pod -n kube-system -l component=kube-apiserver -o yaml | grep encryption-provider-config

8 Vertica 场景的 RBAC 设计

前面几节讲的 RBAC 是通用知识,适用于任何 K8s 工作负载。回到本系列的核心场景:把 Vertica 跑在 K8s 上时,两层 RBAC 都要配——K8s 层控制谁能操作 VerticaDB CR,Vertica 数据库层控制谁能查哪些表。

8.1 K8s 层:控制谁能操作 VerticaDB CR

多团队共享一个 K8s 集群时,每个团队只能管理自己 Namespace 下的 Vertica 数据库。下面这个 Role 赋予 team-alpha 团队对其 VerticaDB 实例的日常运维权限——能查看和修改数据库配置、能看 Pod 日志和事件排查问题,但不能删除数据库、不能修改 RBAC 配置

# 赋予 team-alpha 团队管理自己 Vertica 数据库的权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: team-alpha
  name: vertica-user
rules:
- apiGroups: ["vertica.com"]                              # VerticaDB CRD 的 API 组
  resources: ["verticadbs"]
  verbs: ["get", "list", "watch", "update", "patch"]      # 有 update/patch 能改配置,但没 delete
- apiGroups: [""]
  resources: ["pods", "services", "pods/log", "events"]
  verbs: ["get", "list", "watch"]                         # 能看 Pod 日志排错
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "watch"]                         # 能看 StatefulSet 状态

团队 B 在 team-beta namespace 下有一套同样的 Role + RoleBinding,两个团队互不影响。这就是 K8s RBAC 多租户隔离的核心模式。

8.2 对照学习:Vertica 权限模型 vs K8s RBAC

如果你已经在用 Vertica 的 GRANT/REVOKE 管理数据库权限,以下对照表可以帮你快速建立 K8s RBAC 的心理模型——两套体系的底层逻辑几乎一致。

概念一一对应:

概念 Vertica(数据库内部) K8s RBAC
身份 CREATE USER / CREATE ROLE User / Group / ServiceAccount
权限集合 CREATE ROLE + 在角色上 GRANT 权限 Role / ClusterRole 中的 rules
授权动作 GRANT <role> TO <user> RoleBinding / ClusterRoleBinding
资源分组 Schema(publicvertica Namespace(devteam-alpha
全局资源 系统表、全局 Catalog、License Node、PV、Namespace 对象
操作 SELECT INSERT UPDATE DELETE get list create update delete
只读角色 自定义 role + 只 GRANT SELECT view ClusterRole
管理角色 GRANT role TO user WITH ADMIN OPTION admin ClusterRole
超级管理员 dbadmin 用户 cluster-admin ClusterRole
检查权限 SELECT * FROM grants WHERE grantee='user'(显式授予);
更推荐 GET_PRIVILEGES_DESCRIPTION() 获取合并后有效权限
kubectl auth can-i

⚠️ 关键差异:K8s 不存在「用户表」。 与 Vertica 不同,K8s 中的 User 只是一个外部传来的字符串——K8s 不创建、不存储、不管理用户。没有 kubectl get users,没有 CREATE USER。详情参见上方「3 认证(Authentication)」中的说明。

同一个场景在两套体系中的表达:

场景:运维组(ops)需要对 vertica schema 下的所有表有只读权限,但不能看密码表。

-- Vertica
CREATE ROLE ops_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA vertica TO ops_reader;
REVOKE SELECT ON vertica.passwords FROM ops_reader;
GRANT ops_reader TO alice;
GRANT ops_reader TO bob;
# ========== Role:定义权限规则 ==========
# 对应 Vertica 的 CREATE ROLE + GRANT SELECT ON ...
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: vertica                                 # 作用域 = Vertica Schema
  name: ops-reader                                   # Role 名称 = 角色名
rules:
- apiGroups: [""]                                    # 核心 API 组(Pod、Service 等)
  resources: ["pods", "services", "configmaps"]      # 不包括 "secrets" — 对应 REVOKE SELECT ON passwords
  verbs: ["get", "list", "watch"]                    # 只读三件套,对应 SELECT
---
# ========== RoleBinding:将 Role 授予用户 ==========
# 对应 Vertica 的 GRANT ops_reader TO alice, bob;
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: vertica                            # 必须在同一 Namespace
  name: ops-readers
subjects:                                       # subjects = GRANT ops-readers TO 后面的用户列表
- kind: User
  name: alice
  apiGroup: rbac.authorization.k8s.io
- kind: User
  name: bob
  apiGroup: rbac.authorization.k8s.io
roleRef:                                        # roleRef = 引用上面定义的 Role
  kind: Role
  name: ops-reader                              # 必须与本 Namespace 中的 Role 名称一致
  apiGroup: rbac.authorization.k8s.io

关键差异:

差异点 Vertica K8s RBAC
权限粒度 表级(GRANT SELECT ON t),列级需用 CREATE ACCESS POLICY 资源级别(整个 Pod)
角色嵌套 支持(GRANT role TO role 不支持。最接近的机制是 ClusterRole 聚合(aggregationRule
默认权限 PUBLIC 角色自动分配给所有用户,但仅含最低认证权限;新建用户连 PUBLIC schema 的 USAGE 都没有 默认无权限,所有权限需显式授予
鉴权顺序 先匹配用户权限,再匹配角色权限(权限并集) 先认证后鉴权,所有匹配的 Role 取权限并集

实战启示: K8s RBAC 学习曲线陡的原因不是概念复杂——概念和 Vertica 几乎一一对应——而是 verb 的语义粒度更细(get vs list vs watch 的区别需要厘清)。一旦你把这个对照表记住,K8s RBAC 就只是「把 SQL 的 GRANT 换成 YAML 的 RoleBinding」。

8.3 两层权限的叠加关系

当你把 Vertica 跑在 K8s 上时,存在两层 RBAC

┌──────────────────────────────────────┐
│  Layer 1: K8s RBAC(Operator 层)     │
│  控制:谁能 kubectl apply 一个         │
│  VerticaDB CR 来创建/修改数据库?       │
│  → 决定数据库能不能被部署和管理           │
└──────────────┬───────────────────────┘
               │ 数据库已启动
┌──────────────────────────────────────┐
│  Layer 2: Vertica 权限(数据库层)      │
│  控制:数据库内的用户能查询哪些表?        │
│  能执行哪些 SQL?                      │
│  → 决定数据库内的数据访问范围            │
└──────────────────────────────────────┘

两层互相独立:即使你有 K8s cluster-admin 权限能随意删 Pod,进了 Vertica 数据库里如果没有 SELECT 权限,仍然查不了表。反之也一样——有 Vertica 的 dbadmin 权限也不能阻止别人用 kubectl 删你的 Pod。


9 安全最佳实践清单

  1. 关闭 default ServiceAccount 的自动挂载automountServiceAccountToken: false(Namespace 级别或 Pod 级别)
  2. 每个应用用独立的 ServiceAccount,并授予最小权限
  3. Namespace 级别开启 PSA,默认 baseline,特别权限的 Pod 做例外标注
  4. 开启 Secret 加密(EncryptionConfiguration),定期轮转加密密钥
  5. 使用 NetworkPolicy 实现零信任:默认拒绝所有入站,只开放必要端口
  6. 升级 K8s 版本,及时修复安全漏洞
  7. 审计日志启用:API Server 的 --audit-log-path 记录所有 API 操作
  8. 镜像安全扫描:使用 Trivy、Aqua、Snyk 等工具自动扫描镜像漏洞

10 初学者常见错误

以下是新手配置 RBAC 时最常踩的五个坑:

错误 症状 原因 修复
只创建了 Role,忘了 RoleBinding auth can-i 返回 no Role 定义了权限规则,但没有被绑定到任何人 创建对应的 RoleBinding
RoleBinding 引用了不存在的 Role Binding 创建成功但无效 roleRef.name 拼写错误或引用了另一个 namespace 的 Role kubectl describe rolebinding 检查 roleRef,确认 Role 在同一 namespace
用了 Role 管理集群级资源 403 Forbidden Role 只能管理 Namespaced 资源(Pod、Service 等),Node、PV 等集群级资源必须用 ClusterRole 改用 ClusterRole + ClusterRoleBinding
跨 namespace 引用 Role RoleBinding 创建时报错 RoleBinding 只能引用同一 Namespace 中的 Role 要么在目标 namespace 也创建 Role,要么改用 ClusterRole + RoleBinding
ServiceAccount Token 过期(v1.24+) Pod 突然无法访问 API Server v1.24 后 token 有生命周期,但会自动续期;如果 Pod 创建超过 token 有效期且 kubelet 未成功续期 Pod 重启可触发 token 刷新;排查 kubelet 与 API Server 的连通性

终极建议: 不确定权限配置是否正确?直接用 kubectl auth can-i --as=<用户> 测,比反复修改 YAML 再 apply 快得多。


11 附录:X509 证书认证详解

前面 RBAC 实验中的 simonxiaoming 只是字符串,kubectl 通过 --as= 模拟他们的身份。但在真实环境中,用户需要用证书或 Token 来证明自己就是那个人。X509 客户端证书是最基础的方式——kubectl 和你之间的 TLS 连接不仅是加密通道,证书本身还携带了用户名和组名

核心流程:生成密钥对 → 用 K8s CSR API 让集群签发证书 → 配置 kubectl 使用证书 → 证书里的 CNO 自动变成 K8s 用户和组。以下在 k8s-playground 中走一遍。

11.1 生成私钥 + 证书签名请求(CSR)

genrsa 生成一把 2048 位的 RSA 私钥(.key 文件),这是你的数字签名——永不外传。req -new 用这把私钥生成一个 CSR——你可以理解为「证书申请表」,里面填了你的身份信息(/CN=simon/O=developers),然后用你的私钥签名来证明「确实是私钥持有者提交的这张表」。

#    CN= 映射为 K8s 用户名,O= 映射为组名
openssl genrsa -out simon.key 2048
# -----BEGIN PRIVATE KEY-----
# MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzDa4sJRM7JSsN
# 8dCQOL0M29BUbi4LUaZzvsfGr2sYiVEtiXVeLDUVOX1u6MLWP4hjl/RXzcQxI6hS
# qpQEfGqEMoarEEVy0Vy5bTbY4xjygTCkNy9JFwO0vOvLiOQVHK+u19sl0hok6MAv
# xM1n5uAxYCEmoVxG6u9Ah9vS45i4H89r7ljSuYNzfKAJytTF+X8Vkig49GeLf4Jf
# 9gLWsi+Qvsf0MGrXvLOOLilkldT6sJm7WxgnJrsh5jt5TM+S3NFV10u++ZlFFW4F
# AvmufW4Gf8uV+cMqPtG7aMfcj8RfvI3jQ2NCsZToVf2GI4keaEEj0uB/CElJAwyY
# 621i+uD9AgMBAAECggEAGn3lLbnkkQUsTBlhmt0SN5NUTRGqNVmEz65D/6UXqt8y
# SBME7wSKbBP/94dvwGRRCS9D4zPaGc0lS+naEZnY5qtVGn1DUTwhxHuguFFNcR/l
# Mv7JO76weS7Ukl40JN2ELtHYJk3iOWDIgqjTHVLfC98luIH6VbHP6VPQlfC/uUoX
# Xq5sXzSQltC9iPuv/lFHpumSzBeK5hzd7Zc9HnqLvBTxHqalM3Y7TBOKKbfx+g3U
# tohKPe53Ad1OTLRc35YRWTijeHlJN8GMbahKs1gVf2Liap0wv9bRi7PpGJKz3wA/
# 4L1rtw8P6416jUDqR9xV8YSTt76ppatXH0w0v/zEPwKBgQD8buDtmk4CKf5SP+7V
# cg5j+7e4avOfvyzYGAmY9Dl+q4uj2lpzMbWIWfXYafuWULrXaMaap7j5K/bLdMru
# vk9PUdLK4YYcYFJzq5PffOzv4zAkUZj2NtcbvNYyxPWNkVCCLfsNf2ROofssTH2L
# E1F1WroA7MZDF7PcHDBrDOKR4wKBgQC1lV3qOGiNsues4UjdGTHZUS80qVX9pxOv
# D4oGEaIK/9+kQ3NPSOY2j8VOWF0iRPQq/nqLMbYLxRxvKMdFnlbb5CbOiN4rnbwz
# fU65kxVO/GzYX58VtkIKEZiKi5oLQIZzQRUoRicIZoBAZRRSiIgaMCCrLsSRc1o7
# C0nrAXO3nwKBgFwaeKc47c2MVObdvN7URrvkVBxNqaZXsp0F6DqOoYu+O60FNoth
# T8L4T+MuiEVIH2QJLq2vFKaVi+6gJosFlRpz2F19+9jOrEbYC5Q3mJvOYPpfa1gq
# mkHcnKrZgl6s7psQ+9Do8khega6gGg5sdgRDnipIOe6w70cYYfItOV3RAoGBALS8
# UjkCIlb/vocdyVeAb1R98P16msOZHAd/8QKvZFmMaz5lgI1m4vVlzp53Z2PnvBxb
# JQAM38UBUZb2wLlzq8K8aT/jjTdejee2Dri5RFlU+MW5D3Ad88dv0iP8ZbxTYj+v
# hO6MPOeolnkB4uNvUAC47JtWNoMLjMD+MHm//TzDAoGBANltiW7G5J/cfpanFiAp
# XHRlWHP+IiihohL1uUJROLiQGlPfX0Os9Ur1IMNjbH7kX3YuveeY3l2Pb+0JvWqV
# UwkfCGG0t2RR8kFZ9TY0gw5KxCXKUWyGFusJ1I2NYG86k2OOvWo4ztIoJx5DKkuN
# fDE2qmUBDLkT1BDm118zVJlx
# -----END PRIVATE KEY-----

openssl req -new -key simon.key -out simon.csr -subj "/CN=simon/O=developers"
# -----BEGIN CERTIFICATE REQUEST-----
# MIICajCCAVICAQAwJTEOMAwGA1UEAwwFc2ltb24xEzARBgNVBAoMCmRldmVsb3Bl
# cnMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzDa4sJRM7JSsN8dCQ
# OL0M29BUbi4LUaZzvsfGr2sYiVEtiXVeLDUVOX1u6MLWP4hjl/RXzcQxI6hSqpQE
# fGqEMoarEEVy0Vy5bTbY4xjygTCkNy9JFwO0vOvLiOQVHK+u19sl0hok6MAvxM1n
# 5uAxYCEmoVxG6u9Ah9vS45i4H89r7ljSuYNzfKAJytTF+X8Vkig49GeLf4Jf9gLW
# si+Qvsf0MGrXvLOOLilkldT6sJm7WxgnJrsh5jt5TM+S3NFV10u++ZlFFW4FAvmu
# fW4Gf8uV+cMqPtG7aMfcj8RfvI3jQ2NCsZToVf2GI4keaEEj0uB/CElJAwyY621i
# +uD9AgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEARCQ+SYBIlBA8/h4S52Wym8Km
# EKW8P2FGT6hEIL63J1Ck+hjg2jjl0mGLD6z2Mu5X5ttJyMf17pkvGafActOSaMiW
# XEca3/7n6Hbn7SWvWBmlJ8gEpatA4g9kmxdaUgX7sKxhPNNYlZDTrE5rpNp0y5+G
# 3ti2QZQzPaVF1RWZ8ARUGYQzg7TwSN1KcIru6YCnQqz5iDlKxuCBhCY1hkcFnhAQ
# aJUIjdF2HOkCz+9O+RtBNJkWg6IDCSXtxMFw63eLG5hB9176DVbVvR1p3DnDQwV4
# wYOPZlhSJBnUE6djpPh+d6VdtPqYkOV9YujMXv4UWevYT5JUz+rdwF2MkO8dVQ==
# -----END CERTIFICATE REQUEST-----

11.2 通过 K8s CSR API 提交签名请求

把 CSR 文件 base64 编码后嵌入 K8s 的 CertificateSigningRequest 资源。signerName: kubernetes.io/kube-apiserver-client 告诉 K8s「这是客户端认证证书」。为什么不让 openssl 自签?因为自签证书没有 K8s 集群 CA 的背书,API Server 不信任——CSR API 相当于让 K8s 用自己的 CA 给你正经签发一张证书。

CLUSTER=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}')
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: simon-developers
spec:
  request: $(cat simon.csr | base64 | tr -d '\n')
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # 1 天
  usages:
  - client auth
EOF

11.3 管理员审批

CSR 提交后状态是 Pending,需要 cluster-admin 权限的人(就是你自己)执行 certificate approve。在生产环境中这一步通常是自动化的——但审批这个动作本身就意味着「集群管理员确认这个人的身份」。审批后 K8s CA 签发证书,下载下来就是 .crt 文件。

kubectl certificate approve simon-developers

kubectl get csr simon-developers           # 确认状态是 Approved,Issued
# NAME               AGE     SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
# simon-developers   5m12s   kubernetes.io/kube-apiserver-client   kubernetes-admin   24h                 Approved,Issued

kubectl get csr simon-developers -o jsonpath='{.status.certificate}' | base64 -d > simon.crt
# -----BEGIN CERTIFICATE-----
# MIIDCjCCAfKgAwIBAgIQZjCRNcT6kYOtR2Q5MHd+NjANBgkqhkiG9w0BAQsFADAV
# MRMwEQYDVQQDEwprdWJlcm5ldGVzMB4XDTI2MDczMTEwNTE0N1oXDTI2MDgwMTEw
# NTE0N1owJTETMBEGA1UEChMKZGV2ZWxvcGVyczEOMAwGA1UEAxMFc2ltb24wggEi
# MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzDa4sJRM7JSsN8dCQOL0M29BU
# bi4LUaZzvsfGr2sYiVEtiXVeLDUVOX1u6MLWP4hjl/RXzcQxI6hSqpQEfGqEMoar
# EEVy0Vy5bTbY4xjygTCkNy9JFwO0vOvLiOQVHK+u19sl0hok6MAvxM1n5uAxYCEm
# oVxG6u9Ah9vS45i4H89r7ljSuYNzfKAJytTF+X8Vkig49GeLf4Jf9gLWsi+Qvsf0
# MGrXvLOOLilkldT6sJm7WxgnJrsh5jt5TM+S3NFV10u++ZlFFW4FAvmufW4Gf8uV
# +cMqPtG7aMfcj8RfvI3jQ2NCsZToVf2GI4keaEEj0uB/CElJAwyY621i+uD9AgMB
# AAGjRjBEMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHwYDVR0j
# BBgwFoAUhp5quv3LLj5cURTJQp5BWD2e5AwwDQYJKoZIhvcNAQELBQADggEBABAT
# +NmGNrZqJ9cLcNY6pb7HwakdJjGSlfHGEjfJVQom9ZVJHd3ghqg5KE2AvMu414D4
# Fmow7k+4b/UUljS+9zytyFK5PXEvN7wV3fOUMzIa6Ur11MdzTrnAiQKzcZ5iNSVm
# sIHjRmi5X/YUWLuQXnmlBlXYJNJl4/mB0/poSZH7rHvl5l7VV+XO+LHIfB33OkMr
# cMyeqyQmTjcsAd9j5JYj0JjvaNFXQhItKD1OOukXaQk4GA3rZYuTvz3V+3sQcp0u
# 8fH+MLKGK+FP5IzYrGlXGApHs2Ajx5w+1V5THdIqyp3pH8W2D3OiYLkMHK3Urh6V
# DQM9QCLGJOL33kdEoJE=
# -----END CERTIFICATE-----

11.4 用这个证书创建新的 kubectl 上下文

set-credentials 把证书和私钥注册为一个 kubectl 用户。 set-context 把这个用户和集群绑定为一个「上下文」——可以理解为「以 simon 的身份连接 k8s-playground」。 此时 kubectl config view --minify 能看到当前 kubeconfig 的结构:clusters → users → contexts。

kubectl config set-credentials simon --client-certificate=simon.crt --client-key=simon.key
# User "simon" set.

kubectl config set-context simon-context --cluster=$CLUSTER --user=simon
# Context "simon-context" created.

11.5 切换到 simon 的身份操作集群

--context=simon-context 让 kubectl 使用 simon 的证书发起 TLS 连接。API Server 从证书中提取出 CN=simon(用户名)和 O=developers(组名),然后走 RBAC 鉴权——跟前面 4.6 节 RoleBinding 里的 --user=simon 刚好对上。

kubectl --context=simon-context get pods   # 没绑 RBAC → 应该 403
# Error from server (Forbidden): pods is forbidden: User "simon" cannot list resource "pods" in API group "" in the namespace "default"

kubectl --context=simon-context get pods -n dev  # 没绑 RBAC → 应该 403
# Error from server (Forbidden): pods is forbidden: User "simon" cannot list resource "pods" in API group "" in the namespace "dev"

如果不报错:说明 Kind 默认给了 system:authenticated 组的部分权限(system:discovery+system:basic-user)。 试试 kubectl --context=simon-context get pods -n dev 应该还是 403——因为 simon 没有 dev namespace 的任何 RoleBinding。

11.6 从头绑定:给 simon 授予只读权限

证书让 API Server 认识了 simon,但 RBAC 还没有给他任何权限。4.6 节创建的 simon-pod-reader 如果还在就直接用,不在就重建:

# 确保 simon 绑定了 pod-reader Role(如果还在就跳过创建)
kubectl get rolebinding simon-pod-reader -n dev 2>/dev/null || \
  kubectl create rolebinding simon-pod-reader --role=pod-reader --user=simon -n dev

# 用证书认证后能 list Pod 了!
kubectl --context=simon-context get pods -n dev
# NAME       READY   STATUS    RESTARTS   AGE
# bot-test   1/1     Running   0          173m

# 但删 Pod 还是不行 — pod-reader Role 只有 get/list/watch
kubectl --context=simon-context delete pod bot-test -n dev
# Error from server (Forbidden): pods "bot-test" is forbidden: User "simon" cannot delete resource "pods" in API group "" in the namespace "dev"
# 清理
kubectl delete csr simon-developers
kubectl config delete-context simon-context
kubectl config delete-user simon
rm simon.key simon.csr simon.crt

回顾完整链路: openssl genrsa 生成私钥 → openssl req 生成 CSR(身份信息)→ K8s CSR API 提交 → certificate approve 签发 → 证书里的 CN=simon, O=developers 变成 K8s 用户名和组 → RoleBinding 匹配 User simon → RBAC 鉴权。证书 = 数字身份证,RBAC = 门禁,整条链缺一不可。


快速参考卡片

打印或收藏本节,配置 RBAC 时对照查看。

四对象速查

对象 一句话 作用域
Role 定义「能做什么」的规则 单个 Namespace
ClusterRole 同上,但可管理集群级资源 整个集群
RoleBinding 把 Role 绑定到用户/SA 单个 Namespace
ClusterRoleBinding 把 ClusterRole 绑定到用户/SA 整个集群

记住这个公式

用户/SA  +  RoleBinding  +  Role  =  权限生效
(张三)   +  (任命张三)    +  (岗位说明) = (张三能删 Pod)

Verb 速查

我想... Verb 组合
只看不碰 get, list, watch
可以建但不能删 get, list, watch, create, update, patch
啥都能干 *
读 + 删 get, list, watch, delete

内置 ClusterRole 选型指南

角色 适合谁
cluster-admin 集群管理员(绝对权力)
admin Namespace 管理员(能管所有 Namespaced 资源)
edit 开发者(能部署应用,但不能改 RBAC)
view 只读用户(运维、审计)

调试命令速查

kubectl auth can-i <verb> <resource> -n <ns>              # 问自己
kubectl auth can-i <verb> <resource> --as=<user> -n <ns>  # 替别人问
kubectl describe role <name> -n <ns>                      # 看角色规则
kubectl describe rolebinding <name> -n <ns>               # 看绑定详情
kubectl get rolebinding -A | grep <user>                  # 全局搜某人绑定

Subject 全限定名格式

# User(普通用户)
--user=<用户名>

# Group(用户组)
--group=<组名>

# ServiceAccount
system:serviceaccount:<namespace>:<sa-name>
# 例:system:serviceaccount:dev:bot-reader

PSA 标签速查

kubectl label ns <ns> pod-security.kubernetes.io/enforce=baseline     # 强制
kubectl label ns <ns> pod-security.kubernetes.io/warn=restricted      # 警告
kubectl label ns <ns> pod-security.kubernetes.io/enforce-            # 删除
kubectl get ns <ns> --show-labels                                    # 查看

Token 与 curl 测试

TOKEN=$(kubectl create token <sa> -n <ns>)         # 生成 SA Token
echo $TOKEN | cut -d. -f2 | base64 -d | python3 -m json.tool  # 解码 JWT
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
curl -sk -H "Authorization: Bearer $TOKEN" $APISERVER/api/v1/namespaces/<ns>/pods   # GET
curl -sk -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" $APISERVER/api/v1/namespaces/<ns>/pods -d '{...}'  # POST

下一篇文章: Kubernetes 监控体系 — 学习 K8s 的指标采集、监控告警和自动扩缩容。