Kubernetes RBAC 权限与安全¶
作者:JiangChong | 撰写时间:2026年07月
✅ 学完本文你将能够: ✅ 区分认证和授权的概念及在 K8s 中的分工 ✅ 使用 Role、ClusterRole、RoleBinding 实现权限控制 ✅ 理解 ServiceAccount 的工作原理和 Token 机制 ✅ 配置 Pod Security Admission 和 Secret 加密
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 窃取集群信息
- 被攻破的应用容器可能擅自修改集群资源
- 内部人员可能误操作删除生产环境资源
因此,我们需要一套权限体系来回答三个问题:
- 谁可以访问 API Server?→ 认证
- 他能做什么操作(get、create、delete)?→ 授权(RBAC)
- 他能操作哪些资源(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 的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 交互式登录:
3.3 Webhook Token 认证¶
专业场景:通过外部服务认证 Bearer Token。常用于集成 Vault、Cloud IAM。
3.4 默认绑定¶
K8s 启动后自动创建以下 ClusterRoleBinding,构成安全基线:
| 绑定 | 目标 | 说明 |
|---|---|---|
cluster-admin → system: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
关键输出:
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 证书的
CN和O是怎么变成 K8s 用户名和组的?X509 证书可以理解为一张数字身份证,里面有固定的信息字段。K8s 用其中两个字段来提取身份:
| 证书字段 | 全称 | 映射到 K8s | 示例 |
|---------|------|-----------|------|
|
CN=| Common Name(常用名) | 用户名(User) |CN=zhangsan→ Userzhangsan||
O=| Organization(组织) | 组名(Group) |O=developers→ Groupdevelopers|⚠️
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→ Userzhangsan,提取出O=developers和O=ops→ Groupdevelopers和 Groupops。然后 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中的get和pods正好就是「动词 + 名词」。
| 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)
关键要点: - 小明的只读权限只在
devnamespace 生效,换个 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:
输出会包含 metadata.uid、metadata.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 例子里的 xiaoming、simon 都只是字符串——它们是「人」的身份。但 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(jti、iat、exp都不一样),但它们的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 版本 | latest 或 v1.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-betanamespace 下有一套同样的 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(public、vertica) |
Namespace(dev、team-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)需要对
verticaschema 下的所有表有只读权限,但不能看密码表。
-- 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 的语义粒度更细(
getvslistvswatch的区别需要厘清)。一旦你把这个对照表记住,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 安全最佳实践清单¶
- 关闭 default ServiceAccount 的自动挂载:
automountServiceAccountToken: false(Namespace 级别或 Pod 级别) - 每个应用用独立的 ServiceAccount,并授予最小权限
- Namespace 级别开启 PSA,默认
baseline,特别权限的 Pod 做例外标注 - 开启 Secret 加密(EncryptionConfiguration),定期轮转加密密钥
- 使用 NetworkPolicy 实现零信任:默认拒绝所有入站,只开放必要端口
- 升级 K8s 版本,及时修复安全漏洞
- 审计日志启用:API Server 的
--audit-log-path记录所有 API 操作 - 镜像安全扫描:使用 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 实验中的 simon、xiaoming 只是字符串,kubectl 通过 --as= 模拟他们的身份。但在真实环境中,用户需要用证书或 Token 来证明自己就是那个人。X509 客户端证书是最基础的方式——kubectl 和你之间的 TLS 连接不仅是加密通道,证书本身还携带了用户名和组名。
核心流程:生成密钥对 → 用 K8s CSR API 让集群签发证书 → 配置 kubectl 使用证书 → 证书里的 CN 和 O 自动变成 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没有devnamespace 的任何 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 匹配 Usersimon→ RBAC 鉴权。证书 = 数字身份证,RBAC = 门禁,整条链缺一不可。
快速参考卡片¶
打印或收藏本节,配置 RBAC 时对照查看。
四对象速查¶
| 对象 | 一句话 | 作用域 |
|---|---|---|
| Role | 定义「能做什么」的规则 | 单个 Namespace |
| ClusterRole | 同上,但可管理集群级资源 | 整个集群 |
| RoleBinding | 把 Role 绑定到用户/SA | 单个 Namespace |
| ClusterRoleBinding | 把 ClusterRole 绑定到用户/SA | 整个集群 |
记住这个公式¶
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 的指标采集、监控告警和自动扩缩容。