k8s之rbac

Kubernetes中的RBAC(Role-Based Access Control)是一种授权机制,用于管理和控制对Kubernetes资源的访问权限。RBAC允许管理员定义角色、角色绑定和集群角色绑定来精确控制用户、服务账号或组的权限。

RBAC的核心概念包括以下几个要素:

Role(角色):定义了一组权限,可以授予给特定的命名空间内的用户或服务账号。Role是命名空间级别的授权对象,它只对指定命名空间内的资源起作用。

ClusterRole(集群角色):类似于Role,但作用于整个集群范围内的资源。ClusterRole可以跨越多个命名空间。

RoleBinding(角色绑定):将一个Role绑定到用户、组或服务账号上,以赋予其相应的权限。RoleBinding是命名空间级别的绑定,它将角色授权应用于指定命名空间的主体。

ClusterRoleBinding(集群角色绑定):类似于RoleBinding,但作用于整个集群范围内的资源。

通过RBAC,管理员可以创建和管理角色和绑定,将权限分配给用户或服务账号,从而实现对Kubernetes资源的细粒度访问控制。RBAC提供了灵活的权限管理机制,可以根据需要授予或限制特定操作的执行,以保护集群的安全性和数据的机密性。

RBAC的具体实现是通过Kubernetes API服务器的授权模块实现的,它会验证用户的身份,并根据相应的角色和绑定信息来授予或拒绝对资源的请求。

RBAC在Kubernetes中是一项重要的安全功能,能够帮助管理员有效管理和控制对集群资源的访问权限,并确保只有经过授权的实体能够执行相应的操作。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// pkg/apis/rbac/types.go
// 包括角色、绑定和策略等相关结构体和接口定义
// PolicyRule存储了描述策略规则的信息
type PolicyRule struct {
// 存储支持的Verbs,如"GET","CREATE"..., "*"表示全部支持
Verbs []string

// APIGroups是包含资源的APIGroup的名称。
// ""表示核心API组,"*"表示所有API组。
APIGroups []string

// 规则作用的资源对象,"*"表示指定apiGroups下的所有资源,"*/foo"表示指定apiGroups下所有子资源"foo"
Resources []string

// ResourceNames是规则适用于的可选名称白名单。空集表示允许所有内容。
ResourceNames []string

// NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path
// If an action is not a resource API request, then the URL is split on '/' and is checked against the NonResourceURLs to look for a match.
// Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding.
// Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both.
NonResourceURLs []string
}

// Subject包含对角色绑定适用于的对象或用户标识的引用。它可以包含直接的API对象引用,也可以包含非对象(如用户和组名)的值
type Subject struct {
// 此API组定义的值有"User"、"Group"和"ServiceAccount"。如果授权器不识别该类型值,则应报错
Kind string
// 引用对象所属的APIGroup
// ""表示ServiceAccount subjects
// "rbac.authorization.k8s.io" for User and Group subjects
APIGroup string
// 引用对象名称
Name string
// 引用对象所属的命名空间
// 如果对象类型是非命名空间类型(如"User"或"Group"),并且此值不为空,则授权器应报错
Namespace string
}

// 指向正在使用的角色
type RoleRef struct {
// APIGroup is the group for the resource being referenced
APIGroup string
// Kind is the type of resource being referenced
Kind string
// Name is the name of resource being referenced
Name string
}
// 角色
type Role struct {
metav1.TypeMeta
// Standard object's metadata.
metav1.ObjectMeta

// Rules holds all the PolicyRules for this Role
Rules []PolicyRule
}

// RoleBinding引用一个角色,但不包含角色本身。它可以绑定同一命名空间中的*Role*或ClusterRole
// 它通过Subjects添加了关于用户的信息,并通过所在的命名空间添加了命名空间信息。在给定的命名空间中的RoleBinding只在该命名空间中生效。
type RoleBinding struct {
metav1.TypeMeta
metav1.ObjectMeta

// Subjects holds references to the objects the role applies to.
Subjects []Subject

// RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace.
// If the RoleRef cannot be resolved, the Authorizer must return an error.
// This field is immutable.
RoleRef RoleRef
}

// RoleBinding列表
type RoleBindingList struct {
metav1.TypeMeta
// Standard object's metadata.
metav1.ListMeta

// Items is a list of roleBindings
Items []RoleBinding
}
type RoleList struct {
metav1.TypeMeta
// Standard object's metadata.
metav1.ListMeta

// Items is a list of roles
Items []Role
}

// ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.
type ClusterRole struct {
metav1.TypeMeta
// Standard object's metadata.
metav1.ObjectMeta

// Rules holds all the PolicyRules for this ClusterRole
Rules []PolicyRule

// AggregationRule is an optional field that describes how to build the Rules for this ClusterRole.
// If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be
// stomped by the controller.
AggregationRule *AggregationRule
}

// AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole
type AggregationRule struct {
// ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules.
// If any of the selectors match, then the ClusterRole's permissions will be added
ClusterRoleSelectors []metav1.LabelSelector
}


// ClusterRoleBinding references a ClusterRole, but not contain it. It can reference a ClusterRole in the global namespace,
// and adds who information via Subject.
type ClusterRoleBinding struct {
metav1.TypeMeta
// Standard object's metadata.
metav1.ObjectMeta

// Subjects holds references to the objects the role applies to.
Subjects []Subject

// RoleRef can only reference a ClusterRole in the global namespace.
// If the RoleRef cannot be resolved, the Authorizer must return an error.
// This field is immutable.
RoleRef RoleRef
}


// ClusterRoleBindingList is a collection of ClusterRoleBindings
type ClusterRoleBindingList struct {
metav1.TypeMeta
// Standard object's metadata.
metav1.ListMeta

// Items is a list of ClusterRoleBindings
Items []ClusterRoleBinding
}


// ClusterRoleList is a collection of ClusterRoles
type ClusterRoleList struct {
metav1.TypeMeta
// Standard object's metadata.
metav1.ListMeta

// Items is a list of ClusterRoles
Items []ClusterRole
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// staging/src/k8s.io/apiserver/pkg/server/config.go
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
...
handler := filterlatency.TrackCompleted(apiHandler)
handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
...
}

func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*GenericAPIServer, error) {
...
handlerChainBuilder := func(handler http.Handler) http.Handler {
return c.BuildHandlerChainFunc(handler, c.Config)
}

var debugSocket *routes.DebugSocket
if c.DebugSocketPath != "" {
debugSocket = routes.NewDebugSocket(c.DebugSocketPath)
}

apiServerHandler := NewAPIServerHandler(name, c.Serializer, handlerChainBuilder, delegationTarget.UnprotectedHandler())
...

在一次请求过程中会执行withAuthorization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// staging/src/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
func withAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer, metrics recordAuthorizationMetricsFunc) http.Handler {
if a == nil {
klog.Warning("Authorization is disabled")
return handler
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
authorizationStart := time.Now()

// 提取鉴权所需的信息
attributes, err := GetAuthorizerAttributes(ctx)
if err != nil {
responsewriters.InternalError(w, req, err)
return
}
authorized, reason, err := a.Authorize(ctx, attributes)

authorizationFinish := time.Now()
defer func() {
metrics(ctx, authorized, err, authorizationStart, authorizationFinish)
}()

// an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here.
if authorized == authorizer.DecisionAllow {
audit.AddAuditAnnotations(ctx,
decisionAnnotationKey, decisionAllow,
reasonAnnotationKey, reason)
handler.ServeHTTP(w, req)
return
}
if err != nil {
audit.AddAuditAnnotation(ctx, reasonAnnotationKey, reasonError)
responsewriters.InternalError(w, req, err)
return
}

klog.V(4).InfoS("Forbidden", "URI", req.RequestURI, "reason", reason)
audit.AddAuditAnnotations(ctx,
decisionAnnotationKey, decisionForbid,
reasonAnnotationKey, reason)
responsewriters.Forbidden(ctx, attributes, w, req, reason, s)
})
}


// staging/src/k8s.io/apiserver/pkg/authorization/union/union.go
func (authzHandler unionAuthzHandler) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
var (
errlist []error
reasonlist []string
)
// authzHandler 包括authorizerfactory.privilegedGroupAuthorizer
// node.NodeAuthorizer, rbac.RBACAuthorizer
// 只要任何一个handler返回Allow或者Deny,函数直接返回

for _, currAuthzHandler := range authzHandler {
decision, reason, err := currAuthzHandler.Authorize(ctx, a)

if err != nil {
errlist = append(errlist, err)
}
if len(reason) != 0 {
reasonlist = append(reasonlist, reason)
}
switch decision {
case authorizer.DecisionAllow, authorizer.DecisionDeny:
return decision, reason, err
case authorizer.DecisionNoOpinion:
// continue to the next authorizer
}
}

return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
}

// plugin/pkg/auth/authorizer/rbac/rbac.go
// 如果是RBAC会执行下面的代码进行鉴权
func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes}

r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit)
if ruleCheckingVisitor.allowed {
return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil
}

// Build a detailed log of the denial.
// Make the whole block conditional so we don't do a lot of string-building we won't use.
if klogV := klog.V(5); klogV.Enabled() {
var operation string
if requestAttributes.IsResourceRequest() {
b := &bytes.Buffer{}
b.WriteString(`"`)
b.WriteString(requestAttributes.GetVerb())
b.WriteString(`" resource "`)
b.WriteString(requestAttributes.GetResource())
if len(requestAttributes.GetAPIGroup()) > 0 {
b.WriteString(`.`)
b.WriteString(requestAttributes.GetAPIGroup())
}
if len(requestAttributes.GetSubresource()) > 0 {
b.WriteString(`/`)
b.WriteString(requestAttributes.GetSubresource())
}
b.WriteString(`"`)
if len(requestAttributes.GetName()) > 0 {
b.WriteString(` named "`)
b.WriteString(requestAttributes.GetName())
b.WriteString(`"`)
}
operation = b.String()
} else {
operation = fmt.Sprintf("%q nonResourceURL %q", requestAttributes.GetVerb(), requestAttributes.GetPath())
}

var scope string
if ns := requestAttributes.GetNamespace(); len(ns) > 0 {
scope = fmt.Sprintf("in namespace %q", ns)
} else {
scope = "cluster-wide"
}

klogV.Infof("RBAC: no rules authorize user %q with groups %q to %s %s", requestAttributes.GetUser().GetName(), requestAttributes.GetUser().GetGroups(), operation, scope)
}

reason := ""
if len(ruleCheckingVisitor.errors) > 0 {
reason = fmt.Sprintf("RBAC: %v", utilerrors.NewAggregate(ruleCheckingVisitor.errors))
}
return authorizer.DecisionNoOpinion, reason, nil
}

// pkg/registry/rbac/validation/rule.go
func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
if !visitor(nil, nil, err) {
return
}
} else {
sourceDescriber := &clusterRoleBindingDescriber{}
for _, clusterRoleBinding := range clusterRoleBindings {
subjectIndex, applies := appliesTo(user, clusterRoleBinding.Subjects, "")
if !applies {
continue
}
// 获取角色对应的PolicyRule
rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
if err != nil {
if !visitor(nil, nil, err) {
return
}
continue
}GetSubresource
sourceDescriber.binding = clusterRoleBinding
sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex]
for i := range rules {
if !visitor(sourceDescriber, &rules[i], nil) {
return
}
}
}
}

if len(namespace) > 0 {
if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
if !visitor(nil, nil, err) {
return
}
} else {
sourceDescriber := &roleBindingDescriber{}
for _, roleBinding := range roleBindings {
subjectIndex, applies := appliesTo(user, roleBinding.Subjects, namespace)
if !applies {
continue
}
rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
if !visitor(nil, nil, err) {
return
}
continue
}
sourceDescriber.binding = roleBinding
sourceDescriber.subject = &roleBinding.Subjects[subjectIndex]
for i := range rules {
if !visitor(sourceDescriber, &rules[i], nil) {
return
}
}
}
}
}
}
// 如果通过则修改v.allowed=true,v.reason.并返回false
func (v *authorizingVisitor) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool {
// RuleAllows进行规则匹配
if rule != nil && RuleAllows(v.requestAttributes, rule) {
v.allowed = true
v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String())
return false
}
if err != nil {
v.errors = append(v.errors, err)
}
return true
}

REF:
1.pkg/apis/rbac/types.go
2.staging/src/k8s.io/apiserver/pkg/server/config.go
3.staging/src/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
4.staging/src/k8s.io/apiserver/pkg/authorization/union/union.go
5.plugin/pkg/auth/authorizer/rbac/rbac.go
6.pkg/registry/rbac/validation/rule.go