💡 这篇我整理一下按用户维护 RBAC 清单的方式。按组(group-based)或其他高级授权模式将在后续文章讨论。

背景:mirrord 的 RBAC 现状#

mirrord 是开发者本地调试工具,让开发者的本地进程可以通过集群中的 agent pod 访问 in-cluster 的网络资源(mirrord 是什么、完整 demo 见 《用 mirrord 把本地进程接入 K8s 集群》)。作为集群管理员,如果你想要:

  • ✅ 开发者能在指定 namespace 运行 mirrord
  • ✅ 开发者不能访问其他 namespace
  • ✅ 开发者不能删除应用或改动配置
  • ✅ 权限变化可以审计和回滚

那么你需要给每个开发者颁发 identity(身份凭证)授权清单(RoleBinding)

mirrord 的两层 RBAC 模型#

mirrord 的权限拆分挺有意思,分成两部分:

层级 资源 作用域 目的
命名空间权限 RoleBindingClusterRole/mirrord-developer 某个 namespace 赋予开发者在该 namespace 内 create/delete jobs、port-forward 等能力
cluster-scoped 权限 ClusterRoleBindingClusterRole/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

这些脚本做的本质上就是:

  1. 生成证书 + kubeconfig
  2. 创建 RoleBindingClusterRoleBinding
  3. 删除这些 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
└── ...

工作流变化#

原来的流程(脚本驱动):

管理员 → 手工运行脚本 → 直接创建 binding → 无审计

新的流程(GitOps 驱动):

开发者或管理员 → PR 修改 rbac/live/alice.yaml → 
  评审通过 → merge → 
    Argo CD 自动检测变化 → 
      应用到集群 → 
        ✅ alice 获得权限

撤销权限也是同样的方式:

管理员 → PR 删除或修改 rbac/live/alice.yaml → 
  评审 → merge → 
    Argo CD 自动 sync → 
      删除 binding → 
        ✅ alice 权限回收

按用户维护 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-devteam-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 或 Helm 管理多个用户#

如果用户很多,手工维护每个文件会很繁琐。可以用 KustomizeHelm 生成。

方案 A:Kustomize(简单)#

创建 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

方案 B:Helm(更灵活)#

创建一个 Helm Chart,从 values.yaml 中读取用户列表:

# helm/values.yaml
users:
  - name: alice
    namespaces:
      - team-a-dev
  - name: bob
    namespaces:
      - team-b-dev
  - name: charlie
    namespaces:
      - team-a-dev
      - team-b-dev

模板 helm/templates/rolebinding.yaml

{{- range .Values.users }}
---
{{- range .namespaces }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: mirrord-developer-{{ .name }}
  namespace: {{ . }}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: mirrord-developer
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: {{ .name }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: mirrord-impersonator-{{ .name }}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: mirrord-impersonator
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: {{ .name }}
{{- end }}

第 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

或如果用 Helm 方案:

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: helm
    helm:
      releaseName: mirrord-rbac
      values: |
        users:
          - name: alice
            namespaces:
              - team-a-dev
          - name: bob
            namespaces:
              - team-b-dev        
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

用户身份的管理#

按用户维护 RBAC 的前提是,你有一个稳定的用户身份方案。有两种选择:

选项 1:管理员生成 kubeconfig(当前方案)#

bash rbac/admin/scripts/issue-developer-kubeconfig.sh alice
# → rbac/.credentials/alice.kubeconfig

优点

  • 简单,一命令搞定

缺点

  • 私钥在管理员手中(安全风险)
  • 开发者无法更新或轮换密钥
  • kubeconfig 泄露时,管理员要手工重新签发

选项 2:开发者自己生成,管理员只签名(推荐)#

流程

  1. 开发者本地生成私钥 + CSR

    openssl genrsa -out alice.key 2048
    openssl req -new -key alice.key -out alice.csr -subj "/CN=alice"
    # 把 alice.csr 提交给管理员(或放进 issue/PR)
    
  2. 管理员验证 + 签名

    # 验证身份(是真的 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(GA 之后)里这是必填字段,漏写会被 API server 直接拒掉(spec.usages: Required value)。对 kubernetes.io/kube-apiserver-client 这个 signer,客户端证书认证只需要 client auth;写 server auth 之类会被签发控制器拒签。

    另外提醒两个 heredoc 的坑:分隔符要用不带引号<<EOF(写成 << 'EOF' 会关闭 $(...) 替换,request 字段会塞进字面字符串导致 illegal base64 data);base64 后面的 | tr -d '\n' 不能省(Linux 的 base64 会按 76 列折行,证书数据必须是单行)。

  3. 开发者收到证书,自己拼 kubeconfig

    cat > alice.kubeconfig <<EOF
    apiVersion: v1
    kind: Config
    clusters:
      - name: mirrord-rbac-demo
        cluster:
          server: https://127.0.0.1:6443
          certificate-authority-data: <CA 证书>
    users:
      - name: alice
        user:
          client-certificate-data: $(base64 < alice.crt | tr -d '\n')
          client-key-data: $(base64 < alice.key | tr -d '\n')
    contexts:
      - name: alice@mirrord-rbac-demo
        context:
          cluster: mirrord-rbac-demo
          user: alice
    current-context: alice@mirrord-rbac-demo
    EOF
    chmod 600 alice.kubeconfig
    

优点

  • 私钥永不离开开发者机器
  • 开发者可以自己轮换密钥(重新生成 CSR)
  • 符合 PKI 最佳实践

缺点

  • 多一步操作

权限防护和最小权限原则#

mirrord-developer ClusterRole 包含什么#

现有的 mirrord-developer ClusterRole 授予的权限尽量做到最小化,只包括 mirrord 必需的操作:

- apiGroups: [""]
  resources: ["pods", "services", "configmaps", "secrets", "endpoints"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
  resources: ["jobs"]
  verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["create", "delete"]
- apiGroups: [""]
  resources: ["pods/ephemeralcontainers", "pods/log", "pods/portforward"]
  verbs: ["update", "patch", "get", "create"]

用户不能做什么#

有了这个权限集,开发者 aliceteam-a-dev namespace 中:

不能删除 Deployment
→ 没有 deployments: delete 权限

不能删除 MySQL Pod(由 Deployment 管理)
→ 如果尝试 kubectl delete pod mysql-xxxxx -n team-a-dev,API 服务器返回 403

不能修改 ConfigMap
→ 只有 configmaps: get, list, watch,没有 create, update, patch, delete

不能访问 team-b-dev namespace
→ RoleBinding 只在 team-a-dev 有效

不能 impersonate 其他用户
impersonate users 需要 cluster-scoped 权限,而且也没有

验证权限#

开发者可以检查自己有什么权限:

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)

一个完整的使用示例#

初始化(管理员)#

  1. 部署基础设施

    kubectl apply -f rbac/base/
    # 创建 mirrord-developer 和 mirrord-impersonator ClusterRole
    
  2. 为 alice 生成 kubeconfig

    bash rbac/admin/scripts/issue-developer-kubeconfig.sh alice
    # 输出:rbac/.credentials/alice.kubeconfig
    
  3. 创建初始 RBAC 清单

    cat > rbac/live/alice.yaml <<EOF
    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
    EOF
    
  4. 配置 Argo CD

    kubectl apply -f .argocd/mirrord-rbac-app.yaml
    

使用(开发者)#

export KUBECONFIG=rbac/.credentials/alice.kubeconfig

# 查看自己能访问什么
bash rbac/developer/scripts/whoami.sh

# 运行 mirrord
bash rbac/developer/scripts/run-mirrord.sh

权限变更(管理员 + GitOps)#

场景:alice 需要访问 team-b-dev

  1. 编辑 rbac/live/alice.yaml,添加 team-b-dev 的 RoleBinding

  2. 提交 PR

    git add rbac/live/alice.yaml
    git commit -m "feat(rbac): grant alice access to team-b-dev"
    git push
    
  3. 评审 + merge

  4. Argo CD 自动同步

    # Argo CD 检测到 rbac/live/alice.yaml 变化
    # 自动 kubectl apply
    # alice 很快获得 team-b-dev 访问权(取决于 Argo CD sync 周期)
    

验证

kubectl auth can-i get pods -n team-b-dev --as alice
# yes

权限回收(管理员 + GitOps)#

场景:alice 离职,收回所有权限

  1. 删除 rbac/live/alice.yaml
  2. 提交 PR + merge
  3. Argo CD 自动删除所有 alice 的 binding
  4. 完成

注意:签发后无法「吊销证书」,要回收的是授权#

这里有个容易踩的认知误区。当你已经 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——这才是有效的回收,也正是上面 GitOps 流程在做的事。

想做的事 是否有效 说明
把 approve 改成 deny condition 终态,不可逆
kubectl delete csr ⚠️ 没用 只删对象,已签发证书照样有效到过期
RoleBinding / ClusterRoleBinding 身份还在但没权限,实际等于回收(推荐)
轮换集群 CA ✅ 但是核弹 让所有用户证书一起失效,影响整个集群
等证书过期 取决于签发时设的有效期

这也反过来说明了「证书有效期别设太长」和「用短期凭证 + 可自助轮换」的价值——既然没法吊销,过期就是唯一的兜底。


对比:脚本式 vs GitOps 式#

维度 脚本式 GitOps 式
审计 ❌ 无法追踪谁改了什么 ✅ Git log + PR review
回滚 ❌ 手工脚本,容易出错 git revert 一行搞定
一致性 ❌ 人工易出错 ✅ 自动 reconcile
团队协作 ❌ 需要专人操作 ✅ 任何有权限的人都能提 PR
可观测性 ❌ 脚本执行无记录 ✅ Argo CD UI 可视化
灾备 ❌ 无 ✅ Git 是完整的恢复源

未来:其他授权方式#

按用户维护 RBAC 是比较直接的做法,适合:

  • 用户数少(< 50)
  • 权限关系简单(基本是"是或否")
  • 团队规模不大

但当团队或环境增长时,还有其他方向:

  1. 按 Group 授权(下一篇重点)

    • 用户加入/移出组,由 IdP(OIDC/LDAP)管理
    • Git 里只维护"group → namespace"关系
    • 优点:权限清单爆炸性增长被避免
  2. 按租户或团队授权

    • 每个团队一套权限模板
    • 新人只需分配到团队,权限自动从团队继承
    • 需要 Helm 或 Kustomize 的二阶模板
  3. OIDC + RBAC 绑定

    • 集群配置 OIDC provider
    • 用户身份直接来自 OIDC,不需要 CSR
    • 权限仍然用 RBAC,但 subjects.name 来自 OIDC 的 sub claim
  4. 外部授权系统(如 Keycloak + Gatekeeper)

    • 在 API gateway 层集中管理权限
    • RBAC 作为二层防护
    • 适合大规模多集群场景

本文只讨论按用户的方式,是比较 straightforward 的 GitOps 起点。


总结#

通过把 mirrord RBAC 管理转换成 声明式的 Git YAML,你获得:

  • 可审计:每个权限变更都有 PR 和 commit 记录
  • 可回滚:发现问题直接 git revert
  • 自动化:Argo CD 负责 reconcile,无需手工脚本
  • 透明:团队能看到谁有什么权限
  • 安全:最小权限原则,默认拒绝

启动步骤

  1. 保留现有的 kubeconfig 生成脚本(或改成开发者自生 CSR)
  2. RoleBinding + ClusterRoleBinding 写成 YAML
  3. 放进 rbac/live/ 目录
  4. 配置 Argo CD 或 Flux 自动同步
  5. 后续权限变更走 PR 流程

这就是一个小而完整的、生产级的 mirrord 用户管理系统。


相关阅读(本系列)#

参考#