Repository Reading Site
本轮操作记录:身份、认证、授权、准入实验
前几轮我们已经把: 讲清了一部分。 这轮我切到另外一条必须掌握的主线: 本轮目标分成四层: 1. 先确认这套真实集群的认证、授权、准入基础设施是什么 2. 再创建最小可复现实验环境 3. 用真实返回码区分: 4. 最后把每一步背后的原理写成第六课 --- 因为仓库已经有: 我这轮不是重写旧内容,而是要把它升级成: 仓库原材料够做入门,但还不够达到你要的“能独
本轮操作记录:身份、认证、授权、准入实验
本轮目标
前几轮我们已经把:
kubectl apply主链路- 调度器选节点
- 集群网络
- NetworkPolicy
讲清了一部分。
这轮我切到另外一条必须掌握的主线:
谁在调用 Kubernetes API,为什么它能做某些事,又为什么它会被拒绝。
本轮目标分成四层:
- 先确认这套真实集群的认证、授权、准入基础设施是什么
- 再创建最小可复现实验环境
- 用真实返回码区分:
- 无 token
- 有 token 但无权
- 有 token 且有最小授权
- 最后把每一步背后的原理写成第六课
Step 1:先看仓库里已有资料,避免脱离主线
实际命令
sed -n '1,260p' phase-1/02-configmap-secret.md
sed -n '1,260p' phase-1/04-rbac.md
为什么先看已有文档
因为仓库已经有:
- ConfigMap / Secret 基础材料
- RBAC 基础材料
我这轮不是重写旧内容,而是要把它升级成:
- 更接近真实集群
- 更能解释底层机制
- 更适合排障和架构设计
我得到的结论
仓库原材料够做入门,但还不够达到你要的“能独立搭平台和排障”的层次。
缺的主要是:
- 真实集群证据
- API 请求全链路视角
- token 投影与现代行为
- Admission 与 RBAC 的衔接
所以我决定补一节完整的“身份与权限课”。
Step 2:确认集群里真的有这些 API 资源
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl api-resources | \
rg 'serviceaccounts|roles|rolebindings|clusterroles|clusterrolebindings|secrets'
为什么要做这一步
虽然这些是 Kubernetes 标准对象,但我还是先确认:
- API 资源确实可用
- 没有因为版本、安装方式、权限问题出现异常
我看到了什么
集群里存在:
ServiceAccountRoleRoleBindingClusterRoleClusterRoleBindingSecret
原理解释
这一步本质是在确认:
我们后面的实验,是真正操作 API 资源,而不是建立在假设上。
Step 3:先看现有 ServiceAccount 现状
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl get sa -A
为什么要先看全局现状
因为初学者很容易以为:
- ServiceAccount 很少
- 或者只是教学用
实际上,真实集群里几乎到处都是 ServiceAccount。
系统组件、业务组件、Operator、监控栈、GitOps 控制器,都会用它。
我看到的关键现象
大多数 ServiceAccount 的 SECRETS 列都是:
0
这说明什么
这说明你的集群使用的是现代行为:
- 不再默认给 ServiceAccount 自动创建长期 token Secret
- 更依赖投影卷中的短期 token
这一步非常关键,因为它直接决定了后面文档里该怎么讲 token 模型。
Step 4:先看默认 ServiceAccount 能做什么
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl auth can-i --list \
--as system:serviceaccount:default:default \
-n default
为什么这一步很重要
很多人以为默认 default ServiceAccount 是“完全没权限”。
这是错误的。
它通常至少能做一些:
- 自我权限探测
- API 发现类访问
- 非资源 URL 访问
我看到的关键信息
默认 ServiceAccount 可以访问一些非资源路径,例如:
/api/apis/openapi/version/healthz
但这不代表它有业务资源权限。
原理解释
这一步是在建立一个很重要的认知:
“能访问 API 的某些端点” 和 “能读写资源” 是两回事。
Step 5:直接去控制平面看真实配置
实际命令
ssh root@107.148.176.193 \
'hostname && sudo sed -n "1,260p" /etc/kubernetes/manifests/kube-apiserver.yaml'
为什么不只靠 kubectl
因为很多系统行为,单靠 kubectl 是看不全的。
尤其你以后做平台、做安全、做集群治理时,经常要知道:
- API Server 真正启用了什么模式
- 用什么 key 给 ServiceAccount 签 token
- Admission 插件怎么配的
这些信息最可信的来源之一就是控制平面的静态 Pod 清单。
我先踩到的一个小坑
我先尝试:
ssh us480851516617a ...
ssh 10.10.0.1 ...
连接都被远端关闭。
后来切换成文档里明确写的公网入口:
ssh root@107.148.176.193 ...
成功。
这个坑为什么值得记录
因为以后你做集群排障时,经常会遇到:
- 文档里有多个地址
- 内网地址和公网地址都存在
- SSH 只监听其中一个
这不是“无关细节”,而是实战里非常常见的接入问题。
我确认到的关键配置
真实结果里最关键的是:
--authorization-mode=Node,RBAC--enable-admission-plugins=NodeRestriction--service-account-issuer=https://kubernetes.default.svc.cluster.local--service-account-signing-key-file=/etc/kubernetes/pki/sa.key
原理解释
这说明:
- 授权主要走 Node + RBAC
- 节点侧有
NodeRestriction保护 - ServiceAccount Token 有明确发行者
- Token 是有签名体系的,不是随便拼出来的字符串
Step 6:确认集群里存在真实 Admission 资源
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl get validatingadmissionpolicies,validatingadmissionpolicybindings
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl get validatingwebhookconfiguration monitoring-kube-prometheus-admission -o yaml | \
sed -n '1,240p'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl get mutatingwebhookconfiguration monitoring-kube-prometheus-admission -o yaml | \
sed -n '1,220p'
为什么要看这一步
因为你如果只学 RBAC,会以为:
- 权限通过就万事大吉
但真实集群里,请求还可能继续被 webhook 修改或拒绝。
我看到的结果
集群里存在:
ValidatingWebhookConfiguration/monitoring-kube-prometheus-admissionMutatingWebhookConfiguration/monitoring-kube-prometheus-admission
没有发现:
ValidatingAdmissionPolicyValidatingAdmissionPolicyBinding
原理解释
这说明你的集群目前确实在使用:
- webhook 型准入链
而不是只停留在“Admission 这个概念在书上存在”。
以后你在监控栈 apply PrometheusRule、AlertmanagerConfig 时,如果被拒绝,就应该联想到这层。
Step 7:设计实验对象
我创建了哪些文件
路径:
对象包括:
Namespace/auth-labPod/default-api-clientPod/no-token-api-clientServiceAccount/pod-readerSecret/demo-secretRole/pod-readerRoleBinding/pod-readerPod/reader-api-client
为什么这样设计
这套设计不是随便拼的,而是为了同时拿到三种典型状态:
no-token-api-client- 没有 token 文件
- 观察匿名访问效果
default-api-client- 有默认身份
- 观察认证成功但无授权
reader-api-client- 有最小授权
- 观察允许读 Pod、拒绝读 Secret
为什么还专门放一个 demo-secret
因为如果没有 Secret 这个对照对象,你很难把“普通只读”和“高敏感读取”真正区分开。
Step 8:apply 实验清单
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/06-auth
为什么用目录 apply
因为这一轮实验对象彼此有关联:
- namespace
- sa
- role
- binding
- pod
目录级 apply 可以让整个实验清单具备更好的:
- 可复现性
- 可审阅性
- 可回放性
实际结果
成功创建:
- namespace
- 两个普通 Pod
- 一个绑定最小权限的 Pod
- 一个 ServiceAccount
- 一个 Role
- 一个 RoleBinding
- 一个演示 Secret
Step 9:等待 Pod Ready,先确认实验环境没变量污染
实际命令
kubectl -n auth-lab wait --for=condition=Ready pod/default-api-client --timeout=180s
kubectl -n auth-lab wait --for=condition=Ready pod/no-token-api-client --timeout=180s
kubectl -n auth-lab wait --for=condition=Ready pod/reader-api-client --timeout=180s
kubectl -n auth-lab get pod -o wide
为什么这一步不能省
如果 Pod 还没起来,你后面测试 API 调用失败,就会出现变量污染:
- 到底是权限问题
- 还是 Pod 本身没 Ready
- 还是镜像命令有问题
我确认到的结果
三个 Pod 都 Running / Ready,并且分布在真实工作节点上。
这说明:
- 镜像可拉取
- 命令可执行
- 可以进入下一步权限验证
Step 10:确认默认 Pod 挂载了哪些 ServiceAccount 文件
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n auth-lab exec default-api-client -- \
sh -c 'ls -l /var/run/secrets/kubernetes.io/serviceaccount && echo && cat /var/run/secrets/kubernetes.io/serviceaccount/namespace'
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n auth-lab get pod default-api-client -o yaml | \
sed -n '1,220p'
为什么先看文件和 Pod YAML
因为我要把“ServiceAccount Token”从抽象概念变成真实物件。
我看到的关键内容
目录中真实存在:
tokenca.crtnamespace
Pod YAML 中还看到卷来源是:
serviceAccountTokenconfigMap: kube-root-ca.crtdownwardAPI
原理解释
这说明:
- token 是投影进来的
- CA 证书来自 namespace 中的
kube-root-ca.crt - namespace 信息来自 downward API
这就是容器内访问 Kubernetes API 的最小信任包。
Step 11:确认 automountServiceAccountToken: false 的效果
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n auth-lab exec no-token-api-client -- \
sh -c 'ls -l /var/run/secrets/kubernetes.io/serviceaccount'
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n auth-lab get pod no-token-api-client -o yaml | \
sed -n '1,180p'
我看到的结果
- 容器内目录不存在
- Pod YAML 中明确有:
automountServiceAccountToken: false
- 同时 Pod 仍然显示:
serviceAccountName: default
原理解释
这一步证明了:
“Pod 绑定哪个 ServiceAccount” 和 “是否把 token 自动挂进去” 是两件可以分开的事。
这在安全上很重要,因为很多业务 Pod 根本不需要访问 API。
Step 12:先用 kubectl auth can-i 看权限面
实际命令
printf 'default list pods: '; \
KUBECONFIG=~/.kube/config-k8s-lab kubectl auth can-i list pods \
--as system:serviceaccount:auth-lab:default -n auth-lab
printf 'default get secrets: '; \
KUBECONFIG=~/.kube/config-k8s-lab kubectl auth can-i get secrets \
--as system:serviceaccount:auth-lab:default -n auth-lab
printf 'reader list pods in auth-lab: '; \
KUBECONFIG=~/.kube/config-k8s-lab kubectl auth can-i list pods \
--as system:serviceaccount:auth-lab:pod-reader -n auth-lab
printf 'reader get secrets in auth-lab: '; \
KUBECONFIG=~/.kube/config-k8s-lab kubectl auth can-i get secrets \
--as system:serviceaccount:auth-lab:pod-reader -n auth-lab
printf 'reader list pods in learn-k8s: '; \
KUBECONFIG=~/.kube/config-k8s-lab kubectl auth can-i list pods \
--as system:serviceaccount:auth-lab:pod-reader -n learn-k8s
为什么先用 can-i
因为这是最直接的授权排障入口。
它不会替代真实 API 请求,但能快速回答:
- 这个身份理论上能不能做这个动作
我得到的结果
default list pods: nodefault get secrets: noreader list pods in auth-lab: yesreader get secrets in auth-lab: noreader list pods in learn-k8s: no
原理解释
这一步把 RBAC 的四个维度都展示出来了:
- 身份是谁
- 动作是什么
- 资源是什么
- 作用域在哪个 namespace
Step 13:列出完整权限面,而不是只查单条
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl auth can-i --list \
--as system:serviceaccount:auth-lab:default -n auth-lab | sed -n '1,80p'
KUBECONFIG=~/.kube/config-k8s-lab kubectl auth can-i --list \
--as system:serviceaccount:auth-lab:pod-reader -n auth-lab | sed -n '1,80p'
为什么这一步很有价值
单条 can-i 只能回答一个问题。
而 --list 更适合排障,因为你能直接看出:
- 某个身份完整具备哪些 verbs
- 哪些是非资源 URL 访问
- 哪些是资源访问
我看到的关键信息
默认身份主要只有:
- 自我审查类资源
/api、/apis、/version等非资源 URL
而 pod-reader 多了一条真实业务权限:
pods的get/list/watch
原理解释
这说明我们新增的 Role / RoleBinding 确实生效了,而且生效范围正好是:
auth-lab
没有越界。
Step 14:用容器内真实 API 请求验证“匿名访问”
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n auth-lab exec no-token-api-client -- \
sh -c "echo 'GET /version without token'; \
curl -sS -k -o /tmp/version.out -w 'HTTP %{http_code}\n' https://kubernetes.default.svc/version; \
sed -n '1,40p' /tmp/version.out; \
echo; \
echo 'GET pods in auth-lab without token'; \
curl -sS -k -o /tmp/pods.out -w 'HTTP %{http_code}\n' https://kubernetes.default.svc/api/v1/namespaces/auth-lab/pods; \
sed -n '1,40p' /tmp/pods.out"
为什么这里用了 -k
因为这个 Pod 明确关闭了 token 自动挂载,所以:
- 没有
ca.crt
因此我只能先用 -k 跳过证书校验,专门观察:
- 没有身份材料时,API Server 会把请求当成谁
我看到的结果
GET /version返回HTTP 200GET pods in auth-lab返回HTTP 403- 错误里明确写:
User "system:anonymous"
原理解释
这一步非常重要。
它证明:
- 没有 token 的请求并不是“什么都不是”,而是可能被识别成匿名身份
- 某些非资源 URL 可以匿名访问
- 真正的资源访问仍然会被权限系统拦住
Step 15:用默认 ServiceAccount 访问 API
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n auth-lab exec default-api-client -- \
sh -c 'TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token); \
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt; \
echo "GET /version"; \
curl -sS --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/version; \
echo; echo; \
echo "GET pods in auth-lab"; \
curl -sS -o /tmp/pods.out -w "HTTP %{http_code}\n" \
--cacert $CACERT -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/namespaces/auth-lab/pods; \
sed -n "1,40p" /tmp/pods.out'
这一步体现的三个原理
第一,真正的 API 调用是 HTTPS
不是 http://。
API Server 默认就是:
https://kubernetes.default.svc
第二,Bearer Token 在 HTTP Header 里传
也就是:
Authorization: Bearer <token>
第三,客户端还需要信任 API Server 证书
所以这里用了:
--cacert $CACERT
而不是盲目 -k。
我看到的结果
GET /version成功GET pods in auth-lab返回HTTP 403- 返回里明确写身份是:
system:serviceaccount:auth-lab:default
原理解释
这说明:
- 认证成功
- 但授权失败
这是整个实验里最关键的一条分界线。
Step 16:用最小权限 pod-reader 身份访问 API
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n auth-lab exec reader-api-client -- \
sh -c 'TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token); \
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt; \
echo "GET pods in auth-lab"; \
curl -sS -o /tmp/pods.out -w "HTTP %{http_code}\n" \
--cacert $CACERT -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/namespaces/auth-lab/pods; \
sed -n "1,40p" /tmp/pods.out; \
echo; \
echo "GET secret demo-secret"; \
curl -sS -o /tmp/secret.out -w "HTTP %{http_code}\n" \
--cacert $CACERT -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/namespaces/auth-lab/secrets/demo-secret; \
sed -n "1,40p" /tmp/secret.out'
我看到的结果
- 读取 Pod 列表:
HTTP 200 - 读取
demo-secret:HTTP 403
原理解释
这一步把“最小权限”解释得非常具体:
- 不是说给了一个“读者角色”就什么都能读
- 而是只给了
pods的get/list/watch - 所以 Secret 仍然被拒绝
这正是你以后做生产 RBAC 设计时应该追求的粒度。
Step 17:解码临时 token,只看 payload,不暴露完整凭据
实际命令
TOKEN=$(KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n auth-lab create token pod-reader --duration=1h)
python3 - "$TOKEN" <<'PY'
import sys, json, base64
parts = sys.argv[1].split('.')
payload = parts[1]
payload += '=' * (-len(payload) % 4)
obj = json.loads(base64.urlsafe_b64decode(payload))
keys = ['aud','iss','sub','iat','nbf','exp','jti','kubernetes.io']
print(json.dumps({k: obj.get(k) for k in keys}, ensure_ascii=False, indent=2))
PY
为什么只解码 payload,不打印完整 token
因为完整 token 就是凭据本身。
教学可以看结构,但不能养成把敏感 token 到处复制的坏习惯。
我看到的关键字段
aud = ["https://kubernetes.default.svc.cluster.local"]iss = "https://kubernetes.default.svc.cluster.local"sub = "system:serviceaccount:auth-lab:pod-reader"iat = 2026-04-10 02:20:20 CSTexp = 2026-04-10 03:20:20 CST
原理解释
这一步把几条重要事实串起来了:
- token 是 JWT 风格的结构化令牌
- token 有发行者和受众
- token 有明确过期时间
- token 对应的身份字符串就是 ServiceAccount 身份
这也是为什么我在课程文档里明确强调:
- 现代 ServiceAccount Token 不是“永久门禁卡”
Step 18:确认 kube-root-ca.crt 的来源
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n auth-lab get configmap kube-root-ca.crt -o yaml | sed -n '1,120p'
为什么这一步有必要
很多人会用:
curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt ...
但不知道这个 ca.crt 从哪里来的。
我看到的结果
auth-lab namespace 中真实存在:
ConfigMap/kube-root-ca.crt
原理解释
这说明 Pod 里的 ca.crt 并不是魔法文件,而是来自 namespace 中的根 CA ConfigMap,再由 projected volume 组合进容器目录。
Step 19:把实验对象清单再核对一遍
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n auth-lab get role,rolebinding,secret,serviceaccount,pod -o wide
为什么最后还要做一次总核对
因为我要确保教学文档写下来的内容,和真实集群当前状态完全一致。
我确认到的结果
auth-lab 中当前存在:
role/pod-readerrolebinding/pod-readersecret/demo-secretserviceaccount/defaultserviceaccount/pod-readerpod/default-api-clientpod/no-token-api-clientpod/reader-api-client
原理解释
这一步相当于给整个实验收口,确保:
- 文档中的对象
- 集群中的对象
- 实际观测到的行为
三者是一致的。
本轮最重要的结论
这轮实验最核心的,不是多创建了几个对象,而是把以下几件事变成了“你亲眼看到的事实”:
- Pod 能访问 API Server,不等于 Pod 有业务资源权限。
- 没有 token 时,系统可能把请求当成
system:anonymous。 - 默认 ServiceAccount 往往有少量发现类权限,但默认没有业务资源权限。
- Role / RoleBinding 只会在绑定作用域内生效。
- Secret 权限应被视为高敏感权限,不应顺手就给。
- Admission 是真实存在的链路,不是理论补充项。
- 现代 Kubernetes 中,ServiceAccount 通常不再自动带长期 Secret,而是使用短期投影 token。
这轮命令里值得特别提醒的两个细节
细节 1:kubectl exec 套壳时要小心本地变量展开
我在拼 kubectl exec ... sh -c ... 这类命令时,刻意把这个坑也记录下来。
原因是:
- 本地 shell
kubectl exec- 容器内
sh
是三层解释器。
如果引号处理不好,原本想让容器执行的:
TOKEN=$(cat /var/run/secrets/...)
就可能被本地 shell 先展开,得到完全错误的行为。
这类问题以后在自动化脚本和平台工具里非常常见。
细节 2:curl -k 和 --cacert 不是一回事
-k 的含义是:
- 不校验证书
--cacert 的含义是:
- 明确指定信任的 CA 来校验证书
本轮实验里:
no-tokenPod 没有ca.crt,所以我用-k只为观察匿名身份default/readerPod 有ca.crt,所以我用--cacert做正规 HTTPS 验证
这不是命令细枝末节,而是安全意识差异。
交付物
本轮新增内容包括:
下一轮可以继续接:
- ConfigMap / Secret 深入
- Secret 安全边界
- 投影卷更新机制
- env / volume / subPath 行为差异