前言
上个月帮同事排查权限问题:他给ServiceAccount绑定了Role,但Pod还是无法访问ConfigMap。排查了半天,最后发现Role定义在default namespace,而Pod运行在production namespace。
这个看似简单的问题,让我意识到很多人对RBAC的理解还停留在表面。今天我们就深入源码,看看RBAC鉴权到底是怎么工作的——从ClusterRoleBinding到Rule匹配,完整走一遍四级鉴权流程。
RBAC模型:四种对象的关系
RBAC(Role-Based Access Control)是K8s最主要的鉴权方式,涉及四种核心对象:
┌─────────────────────────────────────────────────────────────────────┐
│ RBAC模型 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 用户/组/ServiceAccount │
│ │ │
│ │ 绑定 (Binding) │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ RoleBinding │ │ ClusterRoleBinding │
│ │ (namespace级) │ │ (集群级) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ │ 引用 (roleRef) │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Role │ │ ClusterRole │ │
│ │ (namespace级) │ │ (集群级) │ │
│ │ │ │ │ │
│ │ rules: │ │ rules: │ │
│ │ - apiGroups │ │ - apiGroups │ │
│ │ - resources │ │ - resources │ │
│ │ - verbs │ │ - verbs │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
四种对象的职责
| 对象 | 作用域 | 职责 | 示例 |
|---|---|---|---|
| Role | Namespace | 定义namespace内的权限 | 可以get Pod |
| ClusterRole | Cluster | 定义集群级的权限 | 可以get Node |
| RoleBinding | Namespace | 将Role绑定到用户 | jane可以get Pod |
| ClusterRoleBinding | Cluster | 将ClusterRole绑定到用户 | jane可以get Node |
重要特性:
- RoleBinding可以引用Role或ClusterRole
- ClusterRoleBinding只能引用ClusterRole
- ClusterRole被RoleBinding引用时,只在binding的namespace内生效
RBAC鉴权流程概览
用户请求
│
▼
┌─────────────────────────────────────────────────────────────┐
│ RBACAuthorizer │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 获取用户身份信息 │
│ - 用户名 │
│ - 用户组 │
│ - namespace │
│ │
│ Step 2: 查找匹配的ClusterRoleBinding │
│ - 遍历所有ClusterRoleBinding │
│ - 检查subject是否匹配用户 │
│ - 获取ClusterRole的rules │
│ - 遍历rules,检查是否允许请求 │
│ │
│ Step 3: 查找匹配的RoleBinding │
│ - 遍历指定namespace的所有RoleBinding │
│ - 检查subject是否匹配用户 │
│ - 获取Role的rules │
│ - 遍历rules,检查是否允许请求 │
│ │
│ Step 4: 返回结果 │
│ - 如果任何rule匹配:DecisionAllow │
│ - 如果都不匹配:DecisionNoOpinion │
│ │
└─────────────────────────────────────────────────────────────┘
源码解析:RBAC鉴权的实现
初始化入口
// pkg/kubeapiserver/authorizer/config.go
case modes.ModeRBAC:
rbacAuthorizer := rbac.New(
// Role列表器
&rbac.RoleGetter{
Lister: config.VersionedInformerFactory.Rbac().V1().Roles().Lister(),
},
// RoleBinding列表器
&rbac.RoleBindingLister{
Lister: config.VersionedInformerFactory.Rbac().V1().RoleBindings().Lister(),
},
// ClusterRole列表器
&rbac.ClusterRoleGetter{
Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoles().Lister(),
},
// ClusterRoleBinding列表器
&rbac.ClusterRoleBindingLister{
Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoleBindings().Lister(),
},
)
authorizers = append(authorizers, rbacAuthorizer)
关键点:使用Informer的Lister,从本地缓存获取数据,避免每次都查询etcd。
RBACAuthorizer结构
// pkg/registry/rbac/authorizer/rbac.go
type RBACAuthorizer struct {
authorizationRuleResolver rbacregistryvalidation.AuthorizationRuleResolver
}
// 创建RBACAuthorizer
func New(
roles rbacregistryvalidation.RoleGetter,
roleBindings rbacregistryvalidation.RoleBindingLister,
clusterRoles rbacregistryvalidation.ClusterRoleGetter,
clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister,
) *RBACAuthorizer {
return &RBACAuthorizer{
authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver(
roles, roleBindings, clusterRoles, clusterRoleBindings,
),
}
}
Authorize方法:鉴权入口
func (r *RBACAuthorizer) Authorize(
ctx context.Context,
requestAttributes authorizer.Attributes,
) (authorizer.Decision, string, error) {
// 创建visitor,用于检查每一条rule
ruleCheckingVisitor := &authorizingVisitor{
requestAttributes: requestAttributes,
}
// 遍历该用户的所有rule,调用visitor.visit检查
r.authorizationRuleResolver.VisitRulesFor(
requestAttributes.GetUser(), // 用户信息
requestAttributes.GetNamespace(), // namespace
ruleCheckingVisitor.visit, // 回调函数
)
// 如果visitor标记了allowed,说明有rule匹配
if ruleCheckingVisitor.allowed {
return authorizer.DecisionAllow,
ruleCheckingVisitor.reason,
nil
}
// 没有匹配的rule
return authorizer.DecisionNoOpinion,
"no RBAC policy matched",
nil
}
VisitRulesFor:遍历所有Rule
这是RBAC鉴权的核心方法,负责遍历用户的所有Role/ClusterRole的rules:
// pkg/registry/rbac/validation/rule.go
func (r *DefaultRuleResolver) VisitRulesFor(
user user.Info,
namespace string,
visitor RuleVisitor,
) {
// ========== 第一步:检查ClusterRoleBinding ==========
// 获取所有ClusterRoleBinding
clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings()
if err != nil {
visitor(nil, nil, err)
return
}
// 遍历每个ClusterRoleBinding
for _, clusterRoleBinding := range clusterRoleBindings {
// 检查subject是否匹配用户
subjectIndex, applies := appliesTo(user, clusterRoleBinding.Subjects, "")
if !applies {
continue // 不匹配,跳过
}
// 获取ClusterRole的rules
rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
if err != nil {
if !visitor(nil, nil, err) {
return
}
continue
}
// 遍历rules,调用visitor检查
sourceDescriber := &ruleSourceDescriber{
binding: clusterRoleBinding,
subject: &clusterRoleBinding.Subjects[subjectIndex],
}
for i := range rules {
if !visitor(sourceDescriber, &rules[i], nil) {
return // visitor返回false,停止遍历
}
}
}
// ========== 第二步:检查RoleBinding ==========
if len(namespace) > 0 {
// 获取该namespace的所有RoleBinding
roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace)
if err != nil {
visitor(nil, nil, err)
return
}
// 遍历每个RoleBinding
for _, roleBinding := range roleBindings {
// 检查subject是否匹配用户
subjectIndex, applies := appliesTo(user, roleBinding.Subjects, namespace)
if !applies {
continue
}
// 获取Role的rules
rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
if !visitor(nil, nil, err) {
return
}
continue
}
// 遍历rules,调用visitor检查
sourceDescriber := &ruleSourceDescriber{
binding: roleBinding,
subject: &roleBinding.Subjects[subjectIndex],
}
for i := range rules {
if !visitor(sourceDescriber, &rules[i], nil) {
return
}
}
}
}
}
appliesTo:Subject匹配逻辑
判断用户是否匹配RoleBinding/ClusterRoleBinding的subject:
// pkg/registry/rbac/validation/policy_comparator.go
func appliesTo(
user user.Info,
subjects []rbacv1.Subject,
namespace string,
) (int, bool) {
for i, subject := range subjects {
if appliesToUser(user, subject, namespace) {
return i, true
}
}
return 0, false
}
func appliesToUser(
user user.Info,
subject rbacv1.Subject,
namespace string,
) bool {
switch subject.Kind {
case rbacv1.UserKind:
// 普通用户:直接比较用户名
return user.GetName() == subject.Name
case rbacv1.GroupKind:
// 用户组:检查用户是否在该组
return has(user.GetGroups(), subject.Name)
case rbacv1.ServiceAccountKind:
// ServiceAccount:比较格式化后的用户名
// ServiceAccount的用户名格式:system:serviceaccount:<namespace>:<name>
saNamespace := namespace
if len(subject.Namespace) > 0 {
saNamespace = subject.Namespace
}
if len(saNamespace) == 0 {
return false
}
return serviceaccount.MatchesUsername(
saNamespace,
subject.Name,
user.GetName(),
)
}
return false
}
ServiceAccount用户名匹配:
// pkg/serviceaccount/util.go
const (
ServiceAccountUsernamePrefix = "system:serviceaccount:"
ServiceAccountUsernameSeparator = ":"
)
// MatchesUsername检查用户名是否匹配namespace和name
func MatchesUsername(namespace, name, username string) bool {
// 检查前缀
if !strings.HasPrefix(username, ServiceAccountUsernamePrefix) {
return false
}
username = username[len(ServiceAccountUsernamePrefix):]
// 检查namespace
if !strings.HasPrefix(username, namespace) {
return false
}
username = username[len(namespace):]
// 检查分隔符
if !strings.HasPrefix(username, ServiceAccountUsernameSeparator) {
return false
}
username = username[len(ServiceAccountUsernameSeparator):]
// 检查name
return username == name
}
// 示例:
// namespace="default", name="my-sa", username="system:serviceaccount:default:my-sa"
// 返回:true
Rule匹配逻辑
当找到匹配的Role/ClusterRole后,需要检查具体的rule是否允许请求。
PolicyRule结构
// staging/src/k8s.io/api/rbac/v1/types.go
type PolicyRule struct {
Verbs []string // 操作类型:get, list, create, update, delete, *
APIGroups []string // API组:""(core), "apps", "rbac.authorization.k8s.io"
Resources []string // 资源类型:pods, services, deployments
ResourceNames []string // 特定资源名(可选)
NonResourceURLs []string // 非资源URL(如/metrics, /healthz)
}
Rule匹配:RuleAllows
// pkg/registry/rbac/validation/policy_comparator.go
func RuleAllows(requestAttributes authorizer.Attributes, rule *rbacv1.PolicyRule) bool {
if requestAttributes.IsResourceRequest() {
// 资源请求匹配
return VerbMatches(rule, requestAttributes.GetVerb()) &&
APIGroupMatches(rule, requestAttributes.GetAPIGroup()) &&
ResourceMatches(rule, requestAttributes.GetResource(), requestAttributes.GetSubresource()) &&
ResourceNameMatches(rule, requestAttributes.GetName())
}
// 非资源请求匹配(如/metrics)
return VerbMatches(rule, requestAttributes.GetVerb()) &&
NonResourceURLMatches(rule, requestAttributes.GetPath())
}
1. Verb匹配
func VerbMatches(rule *rbacv1.PolicyRule, requestedVerb string) bool {
for _, ruleVerb := range rule.Verbs {
// * 通配符匹配所有verb
if ruleVerb == rbacv1.VerbAll {
return true
}
if ruleVerb == requestedVerb {
return true
}
}
return false
}
// 示例:
// rule.Verbs = ["get", "list", "watch"]
// requestedVerb = "get" → 匹配
// requestedVerb = "*" → 不匹配(rule中没有*)
2. API Group匹配
func APIGroupMatches(rule *rbacv1.PolicyRule, requestedGroup string) bool {
for _, ruleGroup := range rule.APIGroups {
// * 通配符匹配所有group
if ruleGroup == rbacv1.APIGroupAll {
return true
}
if ruleGroup == requestedGroup {
return true
}
}
return false
}
// 示例:
// rule.APIGroups = ["", "apps"] // ""表示core group
// requestedGroup = "" → 匹配(core)
// requestedGroup = "apps" → 匹配
// requestedGroup = "batch" → 不匹配
3. Resource匹配
func ResourceMatches(
rule *rbacv1.PolicyRule,
requestedResource,
requestedSubresource string,
) bool {
// 组合resource和subresource(如 pods/status)
combinedResource := requestedResource
if len(requestedSubresource) > 0 {
combinedResource = requestedResource + "/" + requestedSubresource
}
for _, ruleResource := range rule.Resources {
// * 通配符匹配所有resource
if ruleResource == rbacv1.ResourceAll {
return true
}
if ruleResource == combinedResource {
return true
}
// 处理子资源匹配(如rule中有pods,可以匹配pods/status)
if ruleResource == requestedResource {
return true
}
}
return false
}
// 示例:
// rule.Resources = ["pods", "services"]
// requestedResource="pods", requestedSubresource="" → 匹配
// requestedResource="pods", requestedSubresource="status" → 匹配(pods匹配)
// requestedResource="pods", requestedSubresource="log" → 匹配(pods匹配)
// requestedResource="deployments" → 不匹配
4. ResourceName匹配
func ResourceNameMatches(rule *rbacv1.PolicyRule, requestedName string) bool {
// 如果没有指定ResourceNames,允许所有name
if len(rule.ResourceNames) == 0 {
return true
}
// 检查是否匹配特定的resource name
for _, ruleName := range rule.ResourceNames {
if ruleName == requestedName {
return true
}
}
return false
}
// 示例:
// rule.ResourceNames = ["my-pod", "my-configmap"]
// requestedName = "my-pod" → 匹配
// requestedName = "other-pod" → 不匹配
5. 非资源URL匹配
func NonResourceURLMatches(rule *rbacv1.PolicyRule, requestedURL string) bool {
for _, ruleURL := range rule.NonResourceURLs {
// * 通配符匹配所有URL
if ruleURL == rbacv1.NonResourceAll {
return true
}
if ruleURL == requestedURL {
return true
}
// 通配符后缀匹配(如/healthz/*匹配/healthz/ready)
if strings.HasSuffix(ruleURL, "*") &&
strings.HasPrefix(requestedURL, strings.TrimRight(ruleURL, "*")) {
return true
}
}
return false
}
// 示例:
// rule.NonResourceURLs = ["/healthz", "/metrics", "/api/*"]
// requestedURL = "/healthz" → 匹配
// requestedURL = "/metrics" → 匹配
// requestedURL = "/api/v1/pods" → 匹配(/api/*)
// requestedURL = "/debug" → 不匹配
Visitor模式的应用
RBAC鉴权使用了Visitor模式来遍历和检查rules:
// Visitor函数类型
type RuleVisitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool
// authorizingVisitor实现
type authorizingVisitor struct {
requestAttributes authorizer.Attributes
allowed bool
reason string
errors []error
}
func (v *authorizingVisitor) visit(
source fmt.Stringer,
rule *rbacv1.PolicyRule,
err error,
) bool {
if err != nil {
v.errors = append(v.errors, err)
return true // 继续遍历
}
// 检查rule是否匹配请求
if rule != nil && RuleAllows(v.requestAttributes, rule) {
v.allowed = true
v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String())
return false // 找到匹配的rule,停止遍历
}
return true // 继续遍历下一个rule
}
Visitor的好处:
- 将遍历逻辑与检查逻辑分离
- 支持提前终止(找到匹配就停止)
- 收集所有错误信息
使用Informer提升性能
为什么需要Informer?
如果没有Informer,每次鉴权都要从etcd查询:
- List所有ClusterRoleBinding
- List所有RoleBinding
- Get每个Role/ClusterRole
这会造成严重的性能问题。
Informer的工作原理
// 使用SharedInformerFactory创建Lister
versionedInformers.Rbac().V1().Roles().Lister()
versionedInformers.Rbac().V1().RoleBindings().Lister()
versionedInformers.Rbac().V1().ClusterRoles().Lister()
versionedInformers.Rbac().V1().ClusterRoleBindings().Lister()
Informer的工作流程:
etcd ──Watch──→ Informer ──缓存──→ Lister
│
│ 本地内存访问
▼
RBACAuthorizer
优势:
- 本地缓存:所有数据在内存中,查询速度极快
- 实时同步:通过Watch机制保持数据最新
- 共享Informer:多个组件共享同一个Informer,减少apiserver压力
实际案例分析
案例1:ServiceAccount访问ConfigMap
# Role定义
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: configmap-reader
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
# RoleBinding定义
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-configmaps
namespace: production
subjects:
- kind: ServiceAccount
name: my-app
namespace: production
roleRef:
kind: Role
name: configmap-reader
apiGroup: rbac.authorization.k8s.io
鉴权流程:
请求:GET /api/v1/namespaces/production/configmaps/my-config
用户:system:serviceaccount:production:my-app
Step 1: 检查ClusterRoleBinding
- 遍历所有ClusterRoleBinding
- 没有找到匹配的subject
Step 2: 检查RoleBinding(namespace=production)
- 找到RoleBinding: read-configmaps
- subject匹配: ServiceAccount my-app
- 获取Role: configmap-reader
- 检查rules:
- apiGroups: [""] 匹配 (core group)
- resources: ["configmaps"] 匹配
- verbs: ["get"] 匹配
- ✓ 鉴权通过!
案例2:用户访问跨namespace资源失败
# Role定义在default namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default # ← 注意这里是default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# RoleBinding也在default namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane
roleRef:
kind: Role
name: pod-reader
问题:用户jane在production namespace下访问Pod
请求:GET /api/v1/namespaces/production/pods
用户:jane
Step 1: 检查ClusterRoleBinding
- 无匹配
Step 2: 检查RoleBinding(namespace=production)
- production namespace下没有绑定jane的RoleBinding
- 鉴权失败!返回DecisionNoOpinion
解决方案:使用ClusterRoleBinding或ClusterRole。
踩坑实录:RBAC常见问题
坑1:Role和RoleBinding的namespace不一致
现象:用户绑定了Role,但无法访问资源
根因:Role和RoleBinding在不同的namespace
# 错误示例:Role在default,RoleBinding在production
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default # Role在default
name: pod-reader
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: production # RoleBinding在production
解决方案:确保Role和RoleBinding在同一namespace,或者使用ClusterRole。
坑2:resource使用复数形式
现象:rule中写了resource: pod,但权限不生效
根因:应该使用复数形式pods
# 错误
rules:
- apiGroups: [""]
resources: ["pod"] # 应该是pods
verbs: ["get"]
# 正确
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get"]
坑3:subresource没有配置
现象:可以get pod,但不能get pod logs
根因:pod/logs是subresource,需要单独配置
# 错误:缺少logs子资源
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get"]
# 正确:包含pods和pods/log
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get"]
坑4:ClusterRole被RoleBinding引用时的作用域
现象:用RoleBinding绑定ClusterRole,但用户可以在所有namespace操作
误解:ClusterRole被RoleBinding引用后,只在RoleBinding的namespace内生效
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production # 只在production生效
subjects:
- kind: User
name: jane
roleRef:
kind: ClusterRole
name: pod-reader
坑5:ServiceAccount的用户名格式错误
现象:给ServiceAccount绑定了Role,但Pod无法访问
排查:
# 查看ServiceAccount的实际用户名
kubectl auth can-i get pods --as=system:serviceaccount:default:my-sa -n default
# 检查RoleBinding的subject
kubectl get rolebinding -n default -o yaml
常见错误:在RoleBinding中ServiceAccount没有指定namespace
# 错误
subjects:
- kind: ServiceAccount
name: my-sa
# 缺少namespace!
# 正确
subjects:
- kind: ServiceAccount
name: my-sa
namespace: default # 必须指定namespace
RBAC最佳实践
1. 最小权限原则
# 不要这样做
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
# 应该这样做
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"] # 只给需要的权限
2. 使用ClusterRole + RoleBinding组合
# 定义一个通用的ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: configmap-reader
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
# 在每个namespace使用RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-configmaps
namespace: production
subjects:
- kind: ServiceAccount
name: my-app
roleRef:
kind: ClusterRole
name: configmap-reader
优点:
- 避免重复定义Role
- 统一管理权限策略
- 减少配置错误
3. 避免使用cluster-admin
# 不要这样做
roleRef:
kind: ClusterRole
name: cluster-admin # 太危险了!
# 应该创建专用角色
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-operator
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
4. 定期审计权限
# 查看谁有cluster-admin权限
kubectl get clusterrolebindings -o yaml | grep cluster-admin
# 查看ServiceAccount的权限
kubectl auth can-i --list --as=system:serviceaccount:default:my-sa
# 检查未使用的Role
# 使用工具:kubectl-who-can, rbac-lookup等
总结
通过今天的分析,我们深入理解了RBAC鉴权机制:
- 四种对象:Role、ClusterRole、RoleBinding、ClusterRoleBinding
- 四级匹配:ClusterRoleBinding → ClusterRole → RoleBinding → Role
- 五级Rule匹配:Verb → APIGroup → Resource → ResourceName
- Subject匹配:User、Group、ServiceAccount
- 性能优化:使用Informer缓存数据
RBAC是K8s权限管理的基石,理解其工作原理对集群安全至关重要。

139

被折叠的 条评论
为什么被折叠?



