秋风点枝头像
关注
Kubernetes 认证与权限管理:从 kubeconfig 到 RBAC封面图

Kubernetes 认证与权限管理:从 kubeconfig 到 RBAC

Kubernetes 认证与权限管理:从 kubeconfig 到 RBAC

开头

写 [[《Kubernetes 核心网络枢纽:Service 与 Ingress 完全指南》|Service 那篇]] 和 [[k8s中IPVS 三种负载均衡模式详解|IPVS 那篇]] 的时候,我一直在摆弄"流量怎么进集群"。这周方向反过来了:我在搭那个 AI-SRE 项目的 Agent,它得能自己看 Pod、看事件,异常了还能删掉重启。代码在本地 go run 跑得好好的——因为连的是我 Windows 家目录下的 ~/.kube/config。一旦打包成镜像扔进集群,Pod 日志第一行就是:

pods is forbidden: User "system:serviceaccount:default:default"
cannot list resource "pods" in API group "" in the namespace "default"

403 Forbidden。

我当时的第一反应特别朴素:“我本机 kubectl get pods 明明查得到啊,凭什么程序不行?”

然后就卡了一整晚。查资料时满屏的 Authentication、Authorization、Subject、RoleBinding、ClusterRole,每个词都认识,拼在一起就完全不知道谁先谁后、谁管谁。最要命的是我压根没意识到:我平时敲的 kubectl,和 Pod 里的程序,走的是两套完全不同的"身份通道"。kubectl 用的是我本机的证书,Agent 用的是它自己的 ServiceAccount——前者是我,后者是那个 Pod。

这篇就把 K8s 这套"谁能干什么"的机制,从 kubectl 一路捋到 RBAC,最后落到怎么给 Agent 配一套不越权的权限。


1. 为什么 Kubernetes 需要认证与授权

1.1 API Server 是唯一的大门

K8s 里所有操作,本质都是"跟 kube-apiserver 说话"。

kubectl、控制器进程、Pod 里的程序、外面的 CI 流水线,全都是 HTTP 客户端,请求打到同一个 API Server。它不是一个"可以绕开"的服务,而是整个集群的唯一入口——etcd 不对外,调度器不对外,kubelet 也不对外,只有它开着门。你所有的"操作集群",翻译到底层,都是一次对它的 HTTP 请求:

GET https://<apiserver>:6443/api/v1/namespaces/default/pods

搞清楚这一点,后面所有概念就有了落点:认证、授权、准入,全都发生在这一层。

1.2 为什么不能让任何人直接访问

因为 API Server 后面就是整个集群的命根子。

  • 能 list pods,就看得到所有业务拓扑;
  • 能 get secrets,就拿得到数据库密码、镜像仓库凭据、各种 Token;
  • 能 delete deployments,一条命令就能把生产打挂。

kubectl 能通,不是因为它"在集群内部所以免检",而是因为你本机早就带着一份凭证了(下一章讲)。很多人以为 API Server 是"裸奔"的,其实它一直在验人——只是你从没注意过自己是怎么被验的。

还有一个更反直觉的点必须说清楚:Pod 里的程序也需要身份。 我们直觉上会觉得"都在集群内部了,应该算自己人吧",但在 K8s 看来,任何进程只要敲 API Server 的门,就得先自报家门。我的 Agent 想查 Pod,也得先证明它是谁——这正是我开头那个 403 的根。

1.3 认证、授权、准入:三道关卡

一个请求要过三道关,顺序不能乱:

请求
 ↓
Authentication  你是谁?      → 认证失败:401 Unauthorized
 ↓
Authorization   你能干什么?   → 授权失败:403 Forbidden
 ↓
Admission       这个操作合不合规矩?(配额、命名空间是否存在……)
 ↓
写入 etcd / 执行

分工是这样的:

  • Authentication(认证):只负责确认身份,完全不判断权限。它只回答"你是不是你说的那个人"。
  • Authorization(授权):拿认证出来的身份,去查"这个身份能不能做这件事"。我开头那个 403,就卡在这一关。
  • Admission(准入控制):认证授权都过了,还要过一圈准入插件,比如校验 Namespace 是否存在、资源配额够不够、有没有违反 LimitRange。

请把 401 和 403 的区别背下来:

  • 401 = 我没认出你是谁(认证没过,凭证有问题);
  • 403 = 我认出你了,但你没这个权限(认证过了,授权没过)。

这一条能省你半天时间。我那天一开始看到 403 还以为是自己 Token 没挂上,折腾半天才反应过来:认证早就成功了,日志里都写着"User system:serviceaccount:default:default"了——问题根本不在认证,在授权。方向对了,剩下就顺了。


2. K8s 访问控制的整体流程

这是整篇博客最重要的一张图,后面每一章都往这张图上挂:在这里插入图片描述](https://i-blog.csdnimg.cn/direct/29e3d98dc25c44eba2362003e0cda0f0.png)

在这里插入图片描述

记住一句话:认证在前,授权在后,准入垫底。 认证不认人,授权不放行,准入再补一刀。三层各管各的,互不代替。


3. Kubernetes 里的"身份"到底是什么

这一章回答一个问题:

Kubernetes 到底在给"谁"做认证?

K8s 里能被认证的身份有两类:User 和 ServiceAccount。它俩看着像,用起来天差地别,是整个授权模型的起点。

3.1 User

  • 面向人,或者人背后的外部系统;
  • 典型的比如:你用 kubectl 时的 IT 管理员身份、CI 里的发布账号。

这里有个特别容易搞混的点:Kubernetes 里根本没有"User"这个 API 对象。 你 kubectl get users 会直接报错——没有这个资源。

为什么?因为 K8s 把"用户从哪来"这件事交给了外部:

  • 用客户端证书认证时,证书里的 CN(Common Name)就是用户名;
  • 用 OIDC 时,用户名来自身份提供方(比如公司 SSO)的 token;
  • K8s 自己不存用户、不管密码、不给你创建账号。

所以你可以理解为:User 是"外面的人",K8s 只是在请求里认出他是谁,然后当成一个字符串用。 这个字符串后面在 RBAC 里会作为"Subject"出现。

3.2 ServiceAccount

  • 面向 Pod / 程序,是给机器用的身份;
  • 和 User 不一样,ServiceAccount 是实打实的 Kubernetes API 对象,归 K8s 自己管。
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sre-agent-sa
  namespace: default

它有 Namespace(属于某个命名空间)、能 kubectl get sa 查到、能像别的对象一样被创建删除。Pod 只要在 spec.serviceAccountName 里写上名字,就以这个身份运行了。

3.3 User 和 ServiceAccount 的区别

把这张表贴在心里,后面看 RBAC 的 Subject 就不会晕:

UserServiceAccount
面向人 / 外部身份Pod / 程序
是不是 K8s 对象不是(外部管理)是(API 对象)
有没有 Namespace没有有
常见认证方式客户端证书 / OIDCToken
典型使用场景kubectl、人工操作client-go、Controller、Agent
K8s 内如何表示一个字符串(如证书 CN)system:serviceaccount:<ns>:<name>

注意最后一行——ServiceAccount 在 RBAC 里的"用户名"是一长串 system:serviceaccount:default:sre-agent-sa。我排错时就是被这一长串救了命:日志里那句 cannot list ... User "system:serviceaccount:default:default",直接告诉我"哦,用的是 default 命名空间下的 default 这个 SA"。身份没配对,就是它。


4. kubectl 是怎么完成认证的

这一章解释我开头那个疑惑:凭什么本机 kubectl 能查,Pod 里的程序不能?答案就在"你本机带了什么"里面。

4.1 kubectl get pods 背后发生了什么

你敲下:

kubectl get pods

实际上发生的是:

kubectl
  │
  │ 读取 ~/.kube/config(或 $KUBECONFIG 指定的文件)
  ▼
kubeconfig
  │
  ├── API Server 地址
  ├── CA 证书(用来验服务端是不是真的)
  ├── client certificate(我的身份)
  └── client key(我的私钥)
          │
          ▼
   kube-apiserver
          │
     验证证书 → 认出"你是谁"

kubectl 自己没有身份。它每次都在翻一个配置文件,把里面的证书附在 HTTPS 请求上,发给 API Server。API Server 验完证书,就知道"哦,是集群管理员来了"。

这个文件,就是 kubeconfig。

4.2 kubeconfig 是什么

它长这样(结构,不是完整内容):

apiVersion: v1
kind: Config
clusters:            # 去哪里
  - name: minikube
    cluster:
      server: https://127.0.0.1:6443
      certificate-authority-data: <CA 的 base64>
users:               # 我是谁
  - name: minikube
    user:
      client-certificate-data: <我的证书 base64>
      client-key-data: <我的私钥 base64>
contexts:            # 把"去哪里"和"我是谁"绑一起
  - name: minikube
    context:
      cluster: minikube
      user: minikube
      namespace: default
current-context: minikube   # 当前用哪一套

三个核心块,我用自己的话记:

cluster = 去哪里(API Server 地址 + 服务端 CA)
user    = 我是谁(客户端证书 + 私钥,或 Token)
context = 把 cluster 和 user 配对,顺便带一个默认 namespace

为什么要分三层?因为现实里你会有多个集群、多个身份:测试集群用只读账号,生产集群用管理员账号。context 就是"预设组合",kubectl config use-context prod-admin 一敲,等于切换整套"去哪 + 是谁"。

顺手记两个命令:

kubectl config view            # 看结构,敏感字段默认打码
kubectl config view --raw      # 连证书内容一起看(小心,会暴露私钥)
kubectl config current-context # 我现在用的是哪个 context

4.3 客户端证书认证:机制在这里

kubeconfig 里那对 client-certificate / client-key,就是客户端证书认证。它的机制说起来很简单:

client certificate  ┐
client private key  ├─→ 附在请求上 ─→ kube-apiserver
CA                   ┘                    │
                                    用 CA 验证这张证书是不是自己签的
                                          │
                                    看懂证书里的 CN / O

关键在于,证书里有两个字段会被 API Server 直接"翻译"成身份:

  • CN(Common Name)→ 用户名(User)
  • O(Organization)→ 用户所属的组(Group)

也就是说,认证这一关根本不需要查数据库——证书是 CA 签的,只要签名有效,K8s 就相信里面的 CN/O 是真的。这也是为什么证书认证这么快、这么"无状态"。

比如一个证书如果 CN=admin、O=system:masters,那 API Server 就认为"来了个叫 admin 的用户,属于 system:masters 这个组"——而 system:masters 组在 K8s 里是超级管理员(它绑定到了 cluster-admin 这个内置 ClusterRole,后面会见到)。

想看自己 minikube 的证书里到底写了啥:

# --raw 把 base64 解出来,再用 openssl 看证书主体
kubectl config view --raw -o jsonpath='{.users[0].user.client-certificate-data}' \
  | base64 -d | openssl x509 -noout -subject
# subject=O=system:masters, CN=minikube-user

看到 O=system:masters 了吗?这就是为什么我本机 kubectl 有无限权限——不是集群没设防,是我揣着一张"超级管理员工作证"。而 Pod 里的 Agent 用的是另一个身份(ServiceAccount),权限要单独给。两套通道,各论各的。


5. Pod 是怎么完成认证的

这一章直接结合我的 SRE Agent 来讲。

5.1 为什么 Pod 也需要身份

Agent 里有一句典型的 client-go 代码:

pods, err := clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})

翻译成 HTTP,就是:

SRE Agent Pod
       │
       │ GET /api/v1/namespaces/default/pods
       ▼
kube-apiserver

API Server 收到这个请求,第一件事就是问:

这个请求是谁发出来的?

它不会因为"请求来自集群内部"就放行。集群内部一样是威胁面——一个被攻破的 Pod,如果默认就有权限,那整个集群就完了。所以每个 Pod 想调 API,都得先绑定一个身份。

5.2 ServiceAccount:Pod 的身份

在 Deployment 里指定:

spec:
  template:
    spec:
      serviceAccountName: sre-agent-sa   # ← 这个 Pod 以谁的身份运行
      containers:
        - name: sre-agent
          image: sre-agent:v1

于是这个 Pod 的身份链路就成立了:

SRE Agent Pod
      │
      │ 绑定 serviceAccountName
      ▼
sre-agent-sa
      │
      │ 自动获得 Token
      ▼
kube-apiserver

这里有个坑我踩过:如果你不写 serviceAccountName,Pod 会用所在 Namespace 里一个叫 default 的 ServiceAccount。 而 default 这个 SA 默认几乎没有任何权限(就是开头日志里那个 system:serviceaccount:default:default)。所以"忘记指定 SA"和"指定了但没绑权限",报错长得一模一样,都是 403。

5.3 Pod 里的 Token 到底放在哪

K8s 会把 ServiceAccount 的凭证挂成文件塞进容器里,固定路径:

kubectl exec -it <sre-agent-pod> -- ls /var/run/secrets/kubernetes.io/serviceaccount/
# ca.crt      namespace   token

三个文件,各有用途:

文件作用
token证明"我是这个 SA"(Bearer Token)
ca.crt验证 API Server 的证书是不是真的
namespace当前 Pod 所在的命名空间

(顺带一提,从 K8s 1.24 开始,这个 token 不再是以前那种"永久有效、存在 Secret 里"的了,而是短期、会自动轮换的投影 token(projected token),有过期时间、绑定到具体 Pod/audience。老教程里那些"去 Secret 里找 token"的写法,现在要么找不到,要么不安全,别照抄。)

5.4 InClusterConfig():一行代码搞定

那为什么我的 Go 代码里,Pod 里不用手动配 kubeconfig 就能连上?因为 client-go 有个专门的入口:

config, err := rest.InClusterConfig()
if err != nil {
    panic(err.Error())
}
clientset, err := kubernetes.NewForConfig(config)

InClusterConfig() 干的活,就是帮你把上面那套东西自动拼成一份 kubeconfig 等价物:

容器环境变量 KUBERNETES_SERVICE_HOST / KUBERNETES_SERVICE_PORT
              +
/var/run/secrets/kubernetes.io/serviceaccount/token
              +
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
              │
              ▼
      InClusterConfig()
              │
              ▼
     一份"内存里的 kubeconfig"→ clientset

对比一下就很清楚了:

  • 本机 kubectl:读 ~/.kube/config 文件 → 用你个人的证书;
  • Pod 里的程序:InClusterConfig() 读环境变量 + 挂载文件 → 用 Pod 的 ServiceAccount Token。

两套通道,两套身份。这也是我开头那个疑问的完整答案:本机能查是"我这个人有权限",Pod 不能查是"这个 SA 没权限"——压根不是一回事。


6. Authorization:认证成功之后怎么办

先用一句话建立概念:

认证解决"你是谁",授权解决"你能干什么"。

认证通过后,K8s 拿到一个身份(比如 system:serviceaccount:default:sre-agent-sa),然后开始逐条问:

sre-agent-sa
      │
      ▼
能不能:
  get pods ?      ← 看单个 Pod 详情
  list pods ?     ← 列出 Pod 列表(watch / informer 也要它)
  delete pods ?   ← 删 Pod(Agent 自愈要靠它)
  get secrets ?   ← 拿密钥(危险,通常不给)

每一项"能做 / 不能做",都是一次授权判断。

K8s 支持好几种授权模式(Node、RBAC、Webhook、ABAC、AlwaysAllow……),但绝大多数集群默认、且推荐用的是 RBAC——基于角色的访问控制。后面的章节全是围绕它。你会在 kube-apiserver 的启动参数里看到 --authorization-mode=Node,RBAC 这样的配置。


7. RBAC 权限模型

这是整篇的核心章节。RBAC 全称 Role-Based Access Control,翻译过来就是"基于角色的访问控制",四个概念拼起来:

Subject(主体:谁)
   │
   │ Binding(绑定:把他和权限连起来)
   ▼
Role / ClusterRole(角色:一份权限清单)
   │
   ▼
Rules(规则:具体到 对什么资源 做 什么动作)

拆开一个一个看。

7.1 Rule:权限的最小单位

规则就是"对某类资源,允许做某些动作"。最典型的一条:

rules:
  - apiGroups: [""]                    # 哪个 API 组
    resources: ["pods"]                # 哪种资源
    verbs: ["get", "list", "watch"]    # 允许哪些动作

三个字段,是 RBAC 里最容易晕的地方,我一个个拆:

apiGroups(API 组):K8s 的资源按"组"分类。核心资源(pods、services、configmaps)在核心组里,写法是空字符串 ""——这个特别反直觉,我第一次看到 apiGroups: [""] 以为是写漏了。

  • 核心组:""(pods、services、configmaps、secrets、events、nodes…)
  • 批处理:batch(jobs、cronjobs)
  • 应用:apps(deployments、statefulsets、daemonsets)
  • RBAC 自己:rbac.authorization.k8s.io(roles、rolebindings…)

怎么查某个资源的 apiGroup?kubectl api-resources 一列就清楚了:

kubectl api-resources | grep -E "^(pods|deployments|jobs)"
# pods          v1     true    Pod     [""]
# deployments   apps   true    Deployment

resources(资源):就是对什么东西生效,如 pods、deployments、secrets。注意还有子资源,写法是 资源/子资源,比如 pods/log(看日志)、pods/exec(进容器)。我的 Agent 要看日志排障,就得单独给 pods/log 的权限——给 pods 权限并不自动包含 pods/log,这一点坑过我。

verbs(动作):能干什么。常见的有:

verb含义
get查单个对象
list列出对象集合
watch持续监听变化(informer 必需)
create创建
update修改
patch局部修改
delete删除
deletecollection批量删除

重点:list 和 watch 是分开的!client-go 的 informer 机制靠 watch 长连接监听变化,所以只要你的程序用了 informer,就必须同时给 list + watch,光给 list 会一直报错。我一开始只给了 get,结果 Agent 里 list 直接 403,因为"看一个"和"看一堆"是两个不同的动词。

7.2 Role:一份"限定命名空间"的权限清单

Role 就是把上面的 rules 打包,起个名字:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: sre-agent-role
  namespace: default        # ← 关键:Role 属于某个命名空间
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]

记住这条,这是 Role 和 ClusterRole 的分水岭:

Role 是命名空间级的。 上面这个 Role 即使绑给某个身份,那个身份也只能在 default 命名空间里看 Pod。换个命名空间,权限就没有了。

想让它跨命名空间?要么在每个命名空间都建一份,要么用 ClusterRole。


8. RoleBinding:把身份和权限连起来

这是初学者最容易混的地方,我必须强调一遍:光创建 Role 是没有任何效果的。

Role
 ↓
只是一份"权限定义"(一张写好的菜单,还没给任何人)

你还得再写一个 RoleBinding,把"身份"和"Role"连起来:

ServiceAccount  +  Role
        │
        ▼
   RoleBinding("把这份菜单给这个人")

完整写法:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: sre-agent-rolebinding
  namespace: default
subjects:                       # 给谁(可以多个)
  - kind: ServiceAccount        # 主体类型:ServiceAccount / User / Group
    name: sre-agent-sa
    namespace: default          # ServiceAccount 必须带上它的命名空间
roleRef:                        # 给什么权限
  kind: Role                    # 引用 Role 还是 ClusterRole
  name: sre-agent-role
  apiGroup: rbac.authorization.k8s.io

两个 block,一个"给谁",一个"给什么":

  • subjects:主体。kind 可以是 ServiceAccount、User、Group。
  • roleRef:引用哪个 Role/ClusterRole。

这里坑最多,我列三条:

  1. roleRef 里的 apiGroup 必须是 rbac.authorization.k8s.io,不是空字符串。写错了报错很模糊。
  2. roleRef 是不可变的。 RoleBinding 建好之后,你不能改它引用的 Role——想换角色只能删了重建。
  3. subjects 里 ServiceAccount 的 namespace 不能省。 同名 SA 在不同命名空间是不同的身份,K8s 需要这个字段才能唯一定位。

最终的连接关系:

ServiceAccount(sre-agent-sa)
       │
       │ RoleBinding
       ▼
      Role(sre-agent-role)
       │
       ▼
get / list / watch pods

一句话记忆:Role 是"权限清单",RoleBinding 是"发放动作"。两者缺一不可。


9. ClusterRole 与 ClusterRoleBinding

那为什么 K8s 不干脆只用 Role?因为有两类东西压根不属于任何命名空间。

9.1 Role 的边界

上一节说了,Role 是命名空间级的,当它绑到一个身份上时,也只在那个命名空间里生效。

9.2 ClusterRole:集群范围的权限定义

ClusterRole 和 Role 结构几乎一样,但它没有命名空间,可以用来描述两种东西:

  1. 集群级资源(本来就没有命名空间的):nodes、namespaces、persistentvolumes……
  2. 所有命名空间里的资源:比如"允许在全部命名空间里 list pods"。

我的 Agent 要读节点状态(判断是不是节点压力导致的故障),Node 是集群级资源,只能靠 ClusterRole:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: sre-agent-node-reader    # 注意:没有 namespace 字段
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]

9.3 ClusterRoleBinding:把身份绑定到 ClusterRole

对应地,绑定也得用集群级的 ClusterRoleBinding:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: sre-agent-node-reader-binding
subjects:
  - kind: ServiceAccount
    name: sre-agent-sa
    namespace: default
roleRef:
  kind: ClusterRole
  name: sre-agent-node-reader
  apiGroup: rbac.authorization.k8s.io

最终形成这张全景图,看到它就不会混了:

User / ServiceAccount
        │
        ▼
     Binding  ←── RoleBinding(命名空间内) / ClusterRoleBinding(整个集群)
        │
        ▼
Role / ClusterRole          ←── Role 只能在命名空间内;ClusterRole 可以集群级
        │
        ▼
      Rules(apiGroups / resources / verbs)

一个高频面试/实战点:RoleBinding 也能引用 ClusterRole。效果是——把 ClusterRole 里定义的权限,只授予到 RoleBinding 所在的命名空间内。这在实践里非常有用:定义一份通用的 ClusterRole(比如"只读 pods"),然后在每个命名空间用 RoleBinding 引它,权限就被"局部化"了,不会一给就给全集群。反过来,ClusterRoleBinding 一用就是全集群生效,慎用。


10. 实战:给 SRE Agent 配置 RBAC

这一章是整篇的实战部分。目标很明确——我的 Agent 只需要:

list pods       列出 Pod
get pods        查看单个 Pod 详情
watch pods      实时感知 Pod 变化(informer)
get pods/log    拉异常 Pod 的日志
list events     看事件(排查 CrashLoop 的关键)
get/list nodes  看节点状态(判断是不是节点问题)

注意我故意没给 delete pods。Agent 目前只做"观察 + 建议",删除动作走人工确认,这是下一章"最小权限"的伏笔。

10.1 第一步:创建 ServiceAccount

# sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sre-agent-sa
  namespace: default
kubectl apply -f sa.yaml
kubectl get sa sre-agent-sa

10.2 第二步:定义 Role(命名空间级权限)

# role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: sre-agent-role
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods/log"]     # 子资源:看日志要单独授权
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["get", "list"]
kubectl apply -f role.yaml

10.3 第三步:定义 ClusterRole(节点状态)

# clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: sre-agent-node-reader
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
kubectl apply -f clusterrole.yaml

10.4 第四步:两个 Binding,把权限发出去

# rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: sre-agent-rolebinding
  namespace: default
subjects:
  - kind: ServiceAccount
    name: sre-agent-sa
    namespace: default
roleRef:
  kind: Role
  name: sre-agent-role
  apiGroup: rbac.authorization.k8s.io
---
# clusterrolebinding.yaml(可以放同一个文件,用 --- 分隔)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: sre-agent-node-reader-binding
subjects:
  - kind: ServiceAccount
    name: sre-agent-sa
    namespace: default
roleRef:
  kind: ClusterRole
  name: sre-agent-node-reader
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f rolebinding.yaml

10.5 第五步:让 Deployment 用这个 SA

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sre-agent
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sre-agent
  template:
    metadata:
      labels:
        app: sre-agent
    spec:
      serviceAccountName: sre-agent-sa        # ← 关键:绑定身份
      automountServiceAccountToken: true      # 默认就是 true,写出来更清楚
      containers:
        - name: sre-agent
          image: sre-agent:v1
kubectl apply -f deployment.yaml
kubectl get pods -l app=sre-agent

(如果某个 ServiceAccount 根本不需要访问 API,把它设成 automountServiceAccountToken: false,能少一个被利用的凭证——这是安全实践。)

10.6 第六步:验证权限(这一步最关键)

kubectl auth can-i 是授权的"照妖镜",它直接模拟某个身份的授权判断,不用真的跑程序:

# 语法:kubectl auth can-i <动词> <资源> --as=<身份>
kubectl auth can-i get pods \
  --as=system:serviceaccount:default:sre-agent-sa
# yes

kubectl auth can-i list pods \
  --as=system:serviceaccount:default:sre-agent-sa
# yes

kubectl auth can-i get pods/log \
  --as=system:serviceaccount:default:sre-agent-sa
# yes

kubectl auth can-i list nodes \
  --as=system:serviceaccount:default:sre-agent-sa
# yes

kubectl auth can-i delete pods \
  --as=system:serviceaccount:default:sre-agent-sa
# no   ← 正是我们想要的:Agent 不能删 Pod

kubectl auth can-i get secrets \
  --as=system:serviceaccount:default:sre-agent-sa
# no   ← 更不能拿密钥

看到 yes 和 no 交替出现,这套 RBAC 就活了:允许的给 yes,不该给的给 no。 那个 no 比 yes 还重要——它是"最小权限"落地的证据。

再补一个更爽的,一次性列出这个身份的所有权限:

kubectl auth can-i --list \
  --as=system:serviceaccount:default:sre-agent-sa

它会列出一张表,包括从 system:authenticated、system:serviceaccounts 这些默认组继承来的权限。看这张表能发现很多"意料之外的权限",我在排错时靠它就揪出过一个问题。

10.7 真机验证:看 Pod 里到底挂没挂上

前面都是"模拟"。最后进 Pod 里确认真实环境:

kubectl exec -it <sre-agent-pod> -- ls /var/run/secrets/kubernetes.io/serviceaccount/
# ca.crt  namespace  token

# 直接用这个 token 打一次 API,看看返回码
kubectl exec -it <sre-agent-pod> -- sh -c '
  curl -s -o /dev/null -w "%{http_code}\n" \
  --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
  https://kubernetes.default.svc/api/v1/namespaces/default/pods'
# 200  ← 通了

看到 200,说明整条链路(SA → Token → RBAC → 授权通过)都通了。

(补一个 1.27+ 才有的小工具,能直接问"我是谁":)

kubectl auth whoami \
  --as=system:serviceaccount:default:sre-agent-sa
# ATTRIBUTE   VALUE
# Username    system:serviceaccount:default:sre-agent-sa
# Groups      [system:serviceaccounts system:serviceaccounts:default system:authenticated]

注意那个 Groups——ServiceAccount 自动属于 system:serviceaccounts 和 system:serviceaccounts:default 两个组。这意味着你可以给整个"命名空间里所有 SA"或"全集群所有 SA"批量授权(通过给这些组绑 Role),但这也是个风险点,具体给权限时得想清楚。


11. Kubernetes 权限排错

这一章对 SRE 方向特别有用,因为线上最常见的报错,就是程序抛一个 403 Forbidden。

11.1 先区分 401 和 403

拿到报错,第一步不是乱试,是先看是 401 还是 403:

  • 401 Unauthorized:认证没过。凭证本身有问题——Token 没挂上、证书过期、kubeconfig 写错。
  • 403 Forbidden:认证过了,授权没过。身份是对的,就是没权限。

我的那个坑是 403,日志里明确写着 User "system:serviceaccount:default:default"——这行字就是答案:身份是 default SA,不是我以为的 sre-agent-sa。 如果你看到 403 里出现的身份跟你预期的 SA 不一样,那问题在"绑定"环节,不在"授权"环节。

11.2 403 的排查链路

我总结成一条从上到下的检查链,出 403 就顺着往下走:

403 Forbidden
 ↓  认证成功了吗?(401 才是认证问题,403 说明已经认出你了)
 ↓  身份是谁?(看报错里的 User "...")← 先看这一行!
 ↓  ServiceAccount 对吗?(Pod 的 serviceAccountName 写了吗?拼对了吗?)
 ↓  RoleBinding 存在吗?(是不是只建了 Role 没建 Binding?)
 ↓  Role 引用对吗?(roleRef 指向的 Role 名字、apiGroup 对不对?)
 ↓  resource 对吗?(是 pods 还是 pods/log?子资源要单独授权!)
 ↓  verb 对吗?(要 list/watch 只给了 get?)
 ↓  namespace 对吗?(Subject 的 namespace 和 Role 的 namespace 对不对得上?)

这条链子基本能覆盖 90% 的 403。剩下的 10% 才需要翻 kube-apiserver 的审计日志。

11.3 三个救命命令

第一个:kubectl auth can-i——模拟判断,不用真跑程序。

kubectl auth can-i get pods \
  --as=system:serviceaccount:default:sre-agent-sa

第二个:看 Pod 实际用的是哪个 SA——这是我最常用的排查第一步,因为它能直接回答"身份对不对"。

kubectl get pod <pod-name> -o jsonpath='{.spec.serviceAccountName}'
# 如果输出 default,那基本就是没指定 SA 了

第三个:看 RoleBinding 到底绑了谁——确认"发放"环节有没有问题。

kubectl describe rolebinding sre-agent-rolebinding
# Role:  Role/sre-agent-role
# Subjects:
#   Kind            Name          Namespace
#   ServiceAccount  sre-agent-sa  default

Subjects 那几行是不是你想要的绑定,一眼就能看出来。

11.4 进阶:翻 API Server 的日志

如果 can-i 说 yes,但程序还是 403,那说明有一层"你以为的权限"和"实际生效的权限"不一致。这时候去看 kube-apiserver 的日志和审计记录:

# 找到 apiserver 的 Pod
kubectl get pods -n kube-system | grep apiserver

# 翻日志里的 Forbidden(minikube 环境)
kubectl logs -n kube-system <apiserver-pod> | grep -i forbidden

审计日志(如果开了 --audit-policy-file)里,会精确记录每次请求的身份、资源、动词、以及被哪条规则拒绝。这是终极武器,一般用不上,但知道它在哪。


12. 最小权限原则

最后一章讲安全实践。这是整篇的落点,也是为什么我前面死活不给 Agent delete pods。

反面教材长这样:

rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]

这三个星号一写,等于把整个集群交出去了——能看所有密钥、能删所有东西。搜索"K8s 被挖矿"的案例,一堆都是因为某个工作负载的 SA 绑了 cluster-admin 或者通配符权限,被打了之后横向提权,直接把整个集群端掉。

正确姿势是:

SRE Agent
   ↓
只需要
   ↓
get / list / watch pods
get pods/log
get / list events
get / list nodes

即:

程序需要什么权限,就只授予什么权限。多一个都不给。

几条具体的原则,我记在笔记里:

  • 不用通配符。 verbs: ["*"]、resources: ["*"] 一律拆细。

  • secrets 是最危险的资源。 能 get secrets 基本等于能拿到一切凭证,非必要绝对不给。有些安全策略会专门禁止授权 secrets。

  • 优先 get,慎给 list。 list 能一次拿到全部对象,信息暴露面远大于 get 单个。

  • 能用 Role 就别用 ClusterRole,能用 RoleBinding 就别用 ClusterRoleBinding。 范围越小越安全。需要 ClusterRole 时,优先用 RoleBinding 去引用它,把权限限制在单个命名空间。

  • 给机器身份建独立的 SA,别共用 default。 一个 SA 一个用途,出了问题好定位、好回收。

  • 能用 resourceNames 就限定到具体对象。 比如只允许操作某一个特定的 ConfigMap:

    rules:
      - apiGroups: [""]
        resources: ["configmaps"]
        resourceNames: ["my-agent-config"]
        verbs: ["get", "update"]
    

    这样即使身份泄露,能碰的也只有那一个对象。

  • Agent 的"写操作"要人审。 我的设计里 Agent 只做观察和建议,真正的修复动作(删 Pod、改副本数)走人工确认或单独的、权限更大的 SA,跟只读的 Agent 隔开。


13. 总结:把认证授权串起来

最后用一张图收尾,这张图能背下来,K8s 的认证授权就通了:

                 Kubernetes API 请求
                         │
                         ▼
                  kube-apiserver
                         │
                         ▼
                ┌─────────────────┐
                │ Authentication  │   ← 你是谁?
                │     认证         │     失败 = 401
                └────────┬────────┘
                         │
             ┌───────────┴───────────┐
             │                       │
           User               ServiceAccount
             │                       │
      kubeconfig / 证书              Token
      (CN=用户名 O=组)          (挂载在 Pod 里)
             │                       │
             └───────────┬───────────┘
                         ▼
                ┌─────────────────┐
                │ Authorization   │   ← 你能干什么?
                │      RBAC       │     失败 = 403
                └────────┬────────┘
                         │
                         ▼
                 Role / ClusterRole   (权限清单)
                         │
                         ▼
                RoleBinding /        (发放动作)
              ClusterRoleBinding
                         │
                         ▼
                    允许 / 拒绝
                         │
                         ▼
                    Kubernetes API

一句话浓缩:

认证管"你是谁":kubectl 靠证书,Pod 靠 ServiceAccount Token
授权管"你能干什么":RBAC 说了算
RBAC 四件套:Subject --Binding--> Role/ClusterRole --Rules--> 具体权限
Role 管一个命名空间,ClusterRole 管集群级
Role 和 RoleBinding 必须成对出现,只建 Role 等于没建
排错先看 401 还是 403,再看报错里的 User 是谁
最后一条:只给需要的那点权限

我踩过的坑合集

坑我的翻车经历正确做法
Pod 里 403,但本机 kubectl 正常以为程序有 bug,其实两套身份通道Pod 有独立 SA,权限要单独配,别拿本机权限类比
看到 403 却去查 Token折腾半天,其实认证早过了先看报错里的 User "...",403 是授权问题不是认证问题
没指定 serviceAccountName报错里出现 system:serviceaccount:default:default显式声明 SA;看到 default:default 就知道漏配了
只建了 Role 没建 RoleBindingcan-i 一直 no,百思不解Role 和 Binding 必须成对,Role 单建不生效
只给 get 却报 list 403informer 要 list + watch"看一个"和"看一堆"是不同动词,按需给全
给 pods 权限但读不了日志以为 pods 权限含日志pods/log 是子资源,要单独授权
去老教程里翻 Secret 找长 Token1.24 后不再自动创建新版本用投影 Token,kubectl create token <sa> 也能临时签
想给"整个集群只读"却写了 RoleRole 只在一个命名空间生效集群级用 ClusterRole + ClusterRoleBinding
roleRef.apiGroup 写成空字符串apply 报错信息很模糊必须写 rbac.authorization.k8s.io

最后说两句

写这篇的过程,我反复在 minikube 里 kubectl apply、kubectl auth can-i、kubectl describe rolebinding,中间有好几次 can-i 明明该 yes 却返回 no,全靠 describe 一项一项对才找出来。那天晚上我最大的收获其实不是记住了几个字段,而是终于建立了一个清晰的心智模型:任何一次 API 请求,先在脑子里过一遍"它是以谁的身份发的?这个身份有什么权限?",90% 的权限问题当场就能定位。

还没完全搞懂的地方:Admission(准入控制) 我只知道有这一层,但具体的内置准入插件、以及怎么用 webhook 写自定义准入,完全没碰过——这块其实是 K8s 安全的另一大半。还有 OIDC 认证,我只知道它是"接公司 SSO",真实怎么配、怎么和 RBAC 的 User/Group 对起来,也只是概念。审计日志 我翻过几次,但怎么根据审计日志做权限收敛(找出长期没人用的权限并回收),还没实践。

下一步打算:把 Agent 的 SA 权限真正做到最小化——先用 kubectl auth can-i --list 把当前权限全列出来,再对着 Agent 的实际调用(client-go 里到底调了哪些 API)逐条裁剪;然后抽时间搭一个 OIDC,用一张自签证书把"User 证书认证"这条链路自己手搓一遍,彻底把 CN/O → 用户/组的映射跑通。

对了,回看 [[有关docker Namespace原理]]:当时我理解"Namespace 是隔离资源视图",而这篇里 Namespace 又成了 RBAC 权限的边界——同一个词,在隔离和权限两个层面各出现了一次,K8s 真是把"边界"这个概念用到了极致。从 [[《Kubernetes 核心网络枢纽:Service 与 Ingress 完全指南》|Service 那篇]] 的"流量怎么进",到这周的"身份怎么过",集群内部那点事,好像慢慢连成一张网了。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/Zoeww_0319/article/details/166735417

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--