K8s RBAC权限设计:ServiceAccount、Role、ClusterRole的最小权限原则
K8s RBAC权限设计:ServiceAccount、Role、ClusterRole的最小权限原则
适读人群:负责K8s集群安全的工程师 | 阅读时长:约20分钟 | 适用版本:K8s 1.20+
开篇故事
前年我们经历了一次让人胆战心惊的安全事件。一个Java服务存在一个未授权的SSRF漏洞,攻击者通过这个漏洞,让我们的服务向K8s的API Server发了请求。那个Pod用的是default ServiceAccount,而default ServiceAccount在我们集群里被授予了太多权限(之前有人图省事,直接给了ClusterRole)。
攻击者通过SSRF探测到了集群的Service CIDR,然后访问了https://kubernetes.default.svc,拿到了K8s API Server的返回,发现居然能list pods、list secrets,甚至能修改Deployment……虽然攻击者最终没有造成实质伤害,但已经让我们出了一身冷汗。
事后我们做了全面的RBAC整改:所有Pod用专属的ServiceAccount,按最小权限原则授权,default ServiceAccount默认不授予任何权限。这件事教会了我:K8s的RBAC不是可选项,是生产必配。
一、核心问题分析
RBAC的四个核心对象
K8s的RBAC围绕四个核心对象构建:
ServiceAccount:Pod的"身份证",K8s用它来标识Pod的身份,进而决定该Pod能访问哪些K8s API资源。
Role:定义在某个namespace内能做什么(verb)对什么资源(resource)的权限。
ClusterRole:和Role类似,但作用于整个集群(所有namespace + 非namespace资源如Node)。
RoleBinding / ClusterRoleBinding:把Role/ClusterRole绑定到具体的Subject(ServiceAccount、User、Group)。
为什么要重视RBAC
很多团队觉得RBAC繁琐,于是把所有Pod都用default ServiceAccount,然后给default SA加一堆权限。这种做法在集群规模小时没什么问题,但会引入两个严重风险:
一旦任何一个Pod被攻击(SSRF、代码注入等),攻击者可以借助Pod的SA权限在集群内横向移动;过宽的权限使得误操作(如Java代码bug意外调用了K8s API)的影响范围不可控。
二、原理深度解析
RBAC的权限模型
权限决策流程:Pod发起K8s API请求 → API Server提取请求中的ServiceAccount凭证 → 查找绑定到该SA的所有Role/ClusterRole → 检查请求的操作是否在允许的权限列表内 → 允许或拒绝。
三、完整配置实现
普通Java服务的RBAC配置(最小权限)
大多数Java服务不需要访问K8s API,但有些功能需要有限的权限,比如服务注册发现需要list Endpoints,配置热更新需要watch ConfigMap。
# rbac-order-service.yaml
---
# 为order-service创建专属ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: order-service-sa
namespace: production
labels:
app: order-service
annotations:
# 如果使用IRSA(AWS IAM Role for Service Accounts)或Workload Identity
# eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT:role/order-service-role
---
# 定义所需的最小权限Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: order-service-role
namespace: production
rules:
# 只读自己namespace的ConfigMap(用于配置热更新检测)
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["order-service-config"] # 只允许访问特定的ConfigMap
verbs: ["get", "watch"]
# 查看Endpoints(用于服务发现)
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch"]
---
# 将Role绑定到ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: order-service-rolebinding
namespace: production
subjects:
- kind: ServiceAccount
name: order-service-sa
namespace: production
roleRef:
kind: Role
apiGroupname: rbac.authorization.k8s.io
name: order-service-role在Deployment里引用ServiceAccount:
# deployment-with-sa.yaml
spec:
template:
spec:
serviceAccountName: order-service-sa
# 禁止自动挂载ServiceAccount Token
# 如果Pod不需要访问K8s API,应该关闭
automountServiceAccountToken: false
# 如果需要访问K8s API,保持默认true(或不设置)
# automountServiceAccountToken: trueCI/CD流水线的RBAC配置
CI/CD流水线(如ArgoCD、Jenkins)需要较多权限来部署应用,但也要按最小权限原则约束在必要的namespace内:
# rbac-cicd.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: cicd-deployer
namespace: production
---
# CI/CD只需要在production namespace内的部署权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployer-role
namespace: production
rules:
# 允许管理Deployment
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# 允许管理Service
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# 允许管理ConfigMap(部署配置)
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# 允许查看Pod(部署验证)
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
# 允许管理Ingress
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# 禁止访问Secret(CI/CD不应该能看到密钥)
# - apiGroups: [""]
# resources: ["secrets"] # 不授予
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: deployer-rolebinding
namespace: production
subjects:
- kind: ServiceAccount
name: cicd-deployer
namespace: production
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: deployer-role开发人员的RBAC权限分级
# rbac-developers.yaml
---
# 初级开发:只读权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer-readonly
namespace: production
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["pods", "deployments", "services", "ingresses", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
# 注意:Secret只有查看权限,不允许get内容
- apiGroups: [""]
resources: ["secrets"]
verbs: ["list"] # 只能list,不能get(get才能看到内容)
---
# 高级开发:在dev namespace有更多权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer-full
namespace: development # 只在dev环境有完整权限
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
---
# ClusterRole:集群只读(查看Node、Namespace等集群级别资源)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-viewer
rules:
- apiGroups: [""]
resources: ["nodes", "namespaces", "persistentvolumes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]常用的RBAC审计命令
# 查看某个ServiceAccount有哪些权限
kubectl auth can-i --list \
--as=system:serviceaccount:production:order-service-sa \
-n production
# 检查某个SA是否能做某个操作
kubectl auth can-i get pods \
--as=system:serviceaccount:production:order-service-sa \
-n production
# 查看namespace内的所有RoleBinding
kubectl get rolebinding -n production -o wide
# 查看某个用户/SA的所有权限(需要kubectl-who-can插件)
kubectl who-can list pods -n production
# 查看某个ClusterRole的详细规则
kubectl describe clusterrole cluster-viewer
# 检查当前操作者的权限
kubectl auth can-i '*' '*' -n production四、生产最佳实践
分层RBAC策略
在我们的实践中,把角色分成四层:
系统管理员层(ClusterAdmin):只有平台团队有,人数控制在3人以内,用于集群级别的配置管理。
命名空间管理员层(NamespaceAdmin):各业务团队的负责人,在自己的namespace内有完整权限,不能跨namespace操作。
开发者层(Developer):普通开发者,在dev/staging环境有写权限,在production只有读权限。
应用层(ServiceAccount):每个Pod按最小权限配置,99%的业务服务不需要任何K8s API权限。
ServiceAccount Token的安全配置
# 对于不需要访问K8s API的Pod
spec:
automountServiceAccountToken: false # 关闭Token自动挂载
# 对于需要访问K8s API的Pod,使用绑定的Token(有过期时间)
spec:
volumes:
- name: kube-api-access
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600 # 1小时过期
audience: "https://kubernetes.default.svc"五、踩坑实录
坑一:聚合ClusterRole导致权限意外扩大
K8s有个聚合ClusterRole的特性,admin/edit/view这几个内置ClusterRole会聚合带有特定label的其他ClusterRole。如果你创建了一个ClusterRole并打上了聚合label,它的权限会自动合并到对应的内置ClusterRole里。
我们有次给一个CRD(自定义资源)创建了ClusterRole,为了让kubectl edit能工作,打上了rbac.authorization.k8s.io/aggregate-to-edit: "true"的label。结果所有有edit权限的用户都能管理这个CRD了,超出了预期。
要谨慎使用聚合label,或者不使用,手动管理每个角色的权限。
坑二:RoleBinding引用ClusterRole时作用域的混淆
RoleBinding可以绑定ClusterRole,但效果是:ClusterRole里的规则只在RoleBinding所在的namespace内生效,而不是全集群生效。
有个同事以为用ClusterRoleBinding才能跨namespace生效,实际上ClusterRoleBinding绑定的Subject在所有namespace都有权限。这两者的区别在实践中很容易搞混。
记住口诀:跨namespace用ClusterRoleBinding;只在某namespace内生效用RoleBinding(不管绑定的是Role还是ClusterRole)。
坑三:Helm部署时需要的权限比想象中多
用Helm自动创建ServiceAccount和RBAC资源,CI/CD的SA本身需要有权限管理ServiceAccount、Role、RoleBinding这些资源。
但如果你给CI/CD的SA太高权限,它可以通过创建高权限的ServiceAccount并绑定到自己,实现权限提升(Privilege Escalation)。
K8s有防护机制:如果用户A没有某个权限,用户A不能通过创建Role/RoleBinding把该权限授予他人。但这个机制依赖正确的RBAC配置,不能掉以轻心。
六、总结
RBAC的核心原则是最小权限:每个Subject只授予完成其工作所必需的最小权限,不多一点。
具体到Java微服务,90%以上的业务服务根本不需要访问K8s API,直接关闭automountServiceAccountToken是最安全的做法。对于确实需要访问K8s API的服务(如服务发现、配置热更新),用专属的ServiceAccount,按资源类型和操作类型精细授权。
RBAC不是配一次就完事,随着集群和服务的演进,要定期审计权限配置,删除不再需要的RoleBinding,收紧过于宽泛的权限。
