mirrord 用户授权的 GitOps 化:按用户维护 RBAC 清单
目录
这篇整理的是按用户维护 RBAC 清单的做法。需要说明的是,这套还没落到我那个 demo 仓库里:仓库里目前仍是前一篇那套脚本,下面的 YAML 是我打算怎么改的形状,不是已经跑起来的系统。按组(group-based)等授权方式留到后面再写。
背景:mirrord 的 RBAC 现状#
mirrord 是开发者本地调试工具,让开发者的本地进程可以通过集群中的 agent pod 访问 in-cluster 的网络资源(mirrord 是什么、完整 demo 见 用 mirrord 把本地进程接入 K8s 集群)。作为集群管理员,如果你想要:
- 开发者能在指定 namespace 运行 mirrord
- 开发者不能访问其他 namespace
- 开发者不能删除应用或改动配置
- 权限变化可以审计和回滚
那么你需要给每个开发者颁发 identity(身份凭证) 和 授权清单(RoleBinding)。
mirrord 的两层 RBAC 模型#
mirrord 的权限拆分挺有意思,分成两部分:
| 层级 | 资源 | 作用域 | 目的 |
|---|---|---|---|
| 命名空间权限 | RoleBinding → ClusterRole/mirrord-developer |
某个 namespace | 赋予开发者在该 namespace 内 create/delete jobs、port-forward 等能力 |
| cluster-scoped 权限 | ClusterRoleBinding → ClusterRole/mirrord-impersonator |
集群范围 | 赋予开发者 impersonate ServiceAccount 的能力(WebSocket 隧道认证必需) |
这么分的原因是:impersonate serviceaccounts 是 cluster-scoped 虚拟资源,namespace-scoped 的 RoleBinding 无法满足(这条 cluster-scoped 约束的完整原委,见 VS Code 跑 mirrord 撞上 WebSocket 403)。
当前的脚本式操作流程#
现有的实现中(这套脚本和按 namespace 授权的 demo 来自前一篇 给 mirrord 开发者按 namespace 签发 kubeconfig),管理员手工运行:
# 第 1 步:为开发者生成身份凭证
bash rbac/admin/scripts/issue-developer-kubeconfig.sh alice
# 第 2 步:授予 namespace 权限
bash rbac/admin/scripts/grant-namespace-access.sh alice team-a-dev
# 第 3 步:收回权限时
bash rbac/admin/scripts/revoke-namespace-access.sh alice team-a-dev
这些脚本做的本质上就是:
- 生成证书 + kubeconfig
- 创建
RoleBinding和ClusterRoleBinding - 删除这些 binding
问题:
- 授权变更不经过代码审查
- 无法追踪谁在什么时间做了什么
- 无法轻松回滚
- 分布在多个脚本中,容易遗漏
方案:按用户维护 RBAC 清单#
如果你想让 mirrord 授权管理变成 GitOps,比较直接的做法是 把授权关系写成声明式的 YAML,存进 Git,让 Argo CD 或 Flux 去 reconcile。
核心思路#
每个开发者的授权 = 一对 binding YAML 文件 + git 里的声明
# rbac/live/alice.yaml
---
# RoleBinding: alice 在 team-a-dev 里能做什么
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: mirrord-developer-alice
namespace: team-a-dev
labels:
mirrord-rbac-demo/user: alice
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: mirrord-developer
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice # ← 身份来自 kubeconfig
---
# ClusterRoleBinding: alice 能否 impersonate serviceaccounts
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: mirrord-impersonator-alice
labels:
mirrord-rbac-demo/user: alice
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: mirrord-impersonator
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
目录结构建议#
mirrord-demo/
├── rbac/
│ ├── base/
│ │ ├── 01-mirrord-developer-clusterrole.yaml # 基础定义(不变)
│ │ ├── 01b-mirrord-impersonator-clusterrole.yaml # 基础定义(不变)
│ │ └── kustomization.yaml
│ ├── live/
│ │ ├── alice.yaml # alice 的权限绑定
│ │ ├── bob.yaml # bob 的权限绑定
│ │ ├── charlie.yaml # charlie 的权限绑定
│ │ └── kustomization.yaml # 声明所有 user YAML
│ ├── admin/
│ │ ├── scripts/
│ │ │ ├── issue-developer-kubeconfig.sh # 保留:生成 kubeconfig(一次性)
│ │ │ └── lib.sh
│ │ └── manifests/
│ │ ├── 02-namespaces.yaml
│ │ ├── 03-test-workloads.yaml
│ │ └── ...
│ ├── developer/
│ │ └── scripts/
│ │ ├── whoami.sh
│ │ └── run-mirrord.sh
│ └── validate-rbac.sh
├── .argocd/ # ← 新增:Argo CD 配置
│ └── mirrord-rbac-app.yaml
└── ...
工作流变化#
授权和撤销走的是同一条路:改 rbac/live/<user>.yaml、提 PR、评审、merge,剩下的交给 Argo CD reconcile。原来那三个脚本里「直接对集群动手、没有记录」的部分就没有了。
按用户维护 RBAC 的具体步骤#
第 1 步:生成 kubeconfig(一次性,脚本不变)#
开发者身份来自客户端证书。这部分仍然使用现有脚本(CSR + API 签名):
# 管理员执行
bash rbac/admin/scripts/issue-developer-kubeconfig.sh alice
# 输出:rbac/.credentials/alice.kubeconfig
# 这个文件包含:
# - alice.crt(签好的证书)
# - alice.key(私钥)
# - 集群 CA 和 server 地址
# 把 kubeconfig 安全地发给 alice(不走 Git)
安全建议:
- 不要把 kubeconfig 提交进 Git
- 不要把
alice.key放进 Git - 通过 1password、vault 或 secure email 分发
- 或者更好的做法:让开发者自己生成私钥和 CSR,管理员只签名
.crt,私钥永不离开开发者机器
第 2 步:维护 Git 中的授权清单#
根据需要修改 rbac/live/alice.yaml 中的 RoleBinding 和 ClusterRoleBinding。
示例 1:授予 alice 在 team-a-dev 的权限
编辑或创建 rbac/live/alice.yaml:
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: mirrord-developer-alice
namespace: team-a-dev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: mirrord-developer
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: mirrord-impersonator-alice
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: mirrord-impersonator
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
提交 PR:
git add rbac/live/alice.yaml
git commit -m "feat(rbac): grant alice access to team-a-dev namespace"
git push origin feat/alice-team-a-dev
示例 2:授予 alice 在多个 namespace 的权限
如果 alice 需要访问 team-a-dev 和 team-b-dev,把多个 RoleBinding 写在同一个文件里:
---
# team-a-dev
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: mirrord-developer-alice
namespace: team-a-dev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: mirrord-developer
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
---
# team-b-dev
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: mirrord-developer-alice
namespace: team-b-dev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: mirrord-developer
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
---
# Cluster-scoped impersonation(只需一个)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: mirrord-impersonator-alice
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: mirrord-impersonator
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
第 3 步:用 Kustomize 聚合多个用户#
用户多了以后逐个 apply 很繁琐,用一个 kustomization 把 rbac/live/ 下的文件聚合起来,Argo CD 指向这个目录就行。创建 rbac/live/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../base
resources:
- alice.yaml
- bob.yaml
- charlie.yaml
commonLabels:
app.kubernetes.io/part-of: mirrord-rbac
第 4 步:配置 Argo CD 自动同步#
创建 .argocd/mirrord-rbac-app.yaml:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mirrord-rbac
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/meirongdev/mirrord-demo
targetRevision: main
path: rbac/live
destination:
server: https://kubernetes.default.svc
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
prune: true 是这套能「删 YAML 等于收权限」的关键:文件从 Git 里消失,Argo CD 才会把对应的 binding 从集群里删掉。
用户身份的管理#
按用户维护 RBAC 的前提是有一个稳定的身份方案。前一篇那套脚本是管理员本地生成私钥再签发,一条命令搞定,但私钥要过管理员的手,开发者也没法自己轮换。更稳妥的是让开发者自己生成私钥和 CSR,管理员只签名:
# 开发者本地:生成私钥和 CSR,只把 .csr 交出去
openssl genrsa -out alice.key 2048
openssl req -new -key alice.key -out alice.csr -subj "/CN=alice"
# 管理员核实身份之后签名
kubectl apply -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: mirrord-rbac-demo-alice
spec:
request: $(base64 < alice.csr | tr -d '\n')
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
EOF
kubectl certificate approve mirrord-rbac-demo-alice
kubectl get csr mirrord-rbac-demo-alice -o jsonpath='{.status.certificate}' | base64 --decode > alice.crt
这几行我踩过三个坑。spec.usages 在 certificates.k8s.io/v1 里是必填的,漏写会被 API server 直接拒掉(spec.usages: Required value);对 kubernetes.io/kube-apiserver-client 这个 signer 只需要 client auth,写 server auth 之类会被签发控制器拒签。heredoc 的分隔符要用不带引号的 <<EOF,写成 << 'EOF' 会关掉 $(...) 替换,request 里塞进去的就成了字面字符串,报 illegal base64 data。最后 | tr -d '\n' 不能省,Linux 的 base64 按 76 列折行,而证书数据必须是单行。
开发者拿 alice.crt 配上自己的 alice.key 和集群 CA 拼出 kubeconfig,私钥全程没离开过他的机器。
权限边界#
mirrord-developer 和 mirrord-impersonator 这两个 ClusterRole 具体包含哪些规则,前一篇 《给 mirrord 开发者按 namespace 签发 kubeconfig》 里逐条列过,这里不重复贴。要点是它只给 mirrord 跑起来必需的动作:读工作负载、起停 agent Job、port-forward、impersonate 目标 pod 的 ServiceAccount。删 Deployment、改 ConfigMap、访问别的 namespace 都不在里面。
开发者可以自己核一遍拿到的是什么:
export KUBECONFIG=rbac/.credentials/alice.kubeconfig
# 看自己的身份
kubectl auth whoami
# Output: alice
# 看自己在 team-a-dev 能做什么
kubectl auth can-i get pods -n team-a-dev # yes
kubectl auth can-i delete deployments -n team-a-dev # no
kubectl auth can-i list secrets -n team-a-dev # yes
kubectl auth can-i impersonate users -n team-a-dev # no
# 看自己在 team-b-dev 能做什么
kubectl auth can-i get pods -n team-b-dev # no (no binding)
证书签发后无法吊销,能回收的只有授权#
这是我做这套改造时想清楚的一件事,也是「把授权放进 Git」真正的理由。已经 kubectl certificate approve 之后想反悔,会发现:
- approve 不可逆:CSR 的
Approved/Denied是一次性、互斥的终态。一旦 approve,签发控制器会立刻把证书写进.status.certificate,没法再改成 Denied,也没有「反 approve」。 - 删掉 CSR 对象 ≠ 吊销证书:
kubectl delete csr <name>只删集群里那个资源对象,已经签发出去的.crt仍然有效到过期。Kubernetes 的客户端证书认证没有 CRL / 吊销机制,这是它一个有名的限制。
所以真正要「取消」的不是身份(证书决定 CN=alice 是谁),而是授权(RBAC binding 决定 alice 能干什么)。删掉 binding 后,这份证书还能登录、kubectl auth whoami 还认得出 alice,但任何操作都会 403,这才是有效的回收。回到前面的流程:删掉 rbac/live/alice.yaml、merge,Argo CD 靠 prune 把 binding 收走,回收就完成了。
| 想做的事 | 是否有效 | 说明 |
|---|---|---|
| 把 approve 改成 deny | 不行 | condition 是终态,不可逆 |
kubectl delete csr |
没用 | 只删对象,已签发的证书照样有效到过期 |
删 RoleBinding / ClusterRoleBinding |
有效 | 身份还在但没权限,实际等于回收 |
| 轮换集群 CA | 有效,但是核弹 | 所有用户证书一起失效,影响整个集群 |
| 等证书过期 | 有效 | 取决于签发时设的有效期 |
这也反过来说明了「证书有效期别设太长」和「用短期凭证 + 可自助轮换」的价值:既然没法吊销,过期就是唯一的兜底。
小结#
改造本身就三件事:把每个人的 RoleBinding + ClusterRoleBinding 写成一个 YAML 文件放进 rbac/live/,用一个 kustomization 聚合,再让 Argo CD 带 prune 指向这个目录。身份那一半仍然走 CSR 脚本,不进 Git。之后加人、改 namespace、收权限都是同一个动作:改那个文件、提 PR。
相比原来那三个脚本,换来的是变更有 PR 记录、回滚是 git revert、以及集群状态和 Git 声明持续对齐。代价是多一个 Argo CD Application 要维护,以及授权生效从「跑完脚本立刻生效」变成了「merge 之后等一次 sync」。
按用户维护适合我这种用户数不多、权限关系基本是「是或否」的场景。人再多就该按组走 IdP 了,那是另一篇的事。
相关文章#
- 用 mirrord 把本地进程接入 K8s 集群 —— mirrord 是什么、完整 demo
- 给 mirrord 开发者按 namespace 签发 kubeconfig —— 本篇 GitOps 化的脚本前传
- VS Code 跑 mirrord 撞上 WebSocket 403 —— 为什么需要 cluster-scoped impersonator binding
参考资料#
- Kubernetes RBAC 官方文档
- mirrord 文档
- Argo CD 文档
- mirrord-demo 仓库 —— 前一篇那套 CSR 签发与 grant/revoke 脚本;本篇的
rbac/live/和 Argo CD Application 还没推上去