authz鉴权
# 1.简介
# 1.1.strategy
kubernetes支持6种鉴权策略,鉴权策略对应不同鉴权实现,使用的鉴权策略由apiserver配置激活,鉴权模块区分顺序,先执行的优先级更高。const ( // 放行所有请求 ModeAlwaysAllow string = "AlwaysAllow" // 拦截所有请求 ModeAlwaysDeny string = "AlwaysDeny" // 基于属性的访问控制模式,允许基于本地文件配置策略 ModeABAC string = "ABAC" // http回调,允许基于远程REST端点管理鉴权 ModeWebhook string = "Webhook" // 基于角色的访问控制,允许基于kubernetes API创建和存储策略 ModeRBAC string = "RBAC" // 节点鉴权,专用于kubelet ModeNode string = "Node" )1
2
3
4
5
6
7
8
9
10
11
12
13
14
注意
鉴权策略虽然支持
6种,但一般默认只会用到node和RBAC两种
# 1.2.interface
鉴权模块需要实现
Authorizer接口和RuleResolver接口,实现请求鉴权及用户权限解析,根据认证用户获取对应的资源对象的操作权限。// Authorizer makes an authorization decision based on information gained by making // zero or more calls to methods of the Attributes interface. type Authorizer interface { Authorize(ctx context.Context, a Attributes) (authorized Decision, reason string, err error) } // RuleResolver provide a mechanism for resolving the list of rules that apply to a given user within namespace. type RuleResolver interface { RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error) } // 资源规则接口 type ResourceRuleInfo interface { // GetVerbs returns a list of kubernetes resource API verbs. GetVerbs() []string // GetAPIGroups return the names of the APIGroup that contains the resources. GetAPIGroups() []string // GetResources return a list of resources the rule applies to. GetResources() []string // GetResourceNames return a white list of names that the rule applies to. GetResourceNames() []string } // 非资源规则接口 type NonResourceRuleInfo interface { // GetVerbs returns a list of kubernetes resource API verbs. GetVerbs() []string // GetNonResourceURLs return a set of partial urls that a user should have access to. GetNonResourceURLs() []string } // 规则接口实现,存储rules信息 type DefaultResourceRuleInfo struct { Verbs []string APIGroups []string Resources []string ResourceNames []string }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注意
Authorizator需同时实现两个接口,用于注册到联合鉴权模块
# 2.初始化
# 2.1.authorization
Authorizator的初始化与Authenticator类似,基于命令行参数初始化鉴权配置,buildGenericConfig()阶段基于配置初始化鉴权相关对象。// NewServerRunOptions creates a new ServerRunOptions object with default parameters func NewServerRunOptions() *ServerRunOptions { s := ServerRunOptions{ ... Authorization: kubeoptions.NewBuiltInAuthorizationOptions(), ... } ... return &s } // NewBuiltInAuthorizationOptions create a BuiltInAuthorizationOptions with default value func NewBuiltInAuthorizationOptions() *BuiltInAuthorizationOptions { return &BuiltInAuthorizationOptions{ Modes: []string{authzmodes.ModeAlwaysAllow}, // 默认放行所有请求 WebhookVersion: "v1beta1", WebhookCacheAuthorizedTTL: 5 * time.Minute, WebhookCacheUnauthorizedTTL: 30 * time.Second, WebhookRetryBackoff: genericoptions.DefaultAuthWebhookRetryBackoff(), } } // BuildGenericConfig takes the master server options and produces the genericapiserver.Config. func buildGenericConfig(s *options.ServerRunOptions, proxyTransport *http.Transport) (...) { ... genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers) ... return } // BuildAuthorizer constructs the authorizer func BuildAuthorizer(...) (authorizer.Authorizer, authorizer.RuleResolver, error) { // 命令行配置转到鉴权配置 authorizationConfig := s.Authorization.ToAuthorizationConfig(versionedInformers) // 连接拨号出口选择 if EgressSelector != nil { egressDialer, err := EgressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext()) ... authorizationConfig.CustomDial = egressDialer } // 初始化鉴权器 return authorizationConfig.New() }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注意
Authorizator类似于认证,不同的鉴权策略串联为组合鉴权链,依次执行请求鉴权
# 2.2.authzHandler
authorizationConfig.New()用于根据配置动态构建鉴权器和规则解析器,将激活的鉴权策略加入union Authorizator以达到组合鉴权目的。// New returns the right sort of union of multiple authorizer. func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, error) { ... // 加入特权authorizer,直接放行system:masters特权组请求 superuserAuthorizer := authorizerfactory.NewPrivilegedGroups(user.SystemPrivilegedGroup) authorizers = append(authorizers, superuserAuthorizer) // 遍历激活的鉴权策略 for _, authorizationMode := range config.AuthorizationModes { // 不同策略对应不同实现 switch authorizationMode { case modes.ModeNode: ... graph := node.NewGraph() // 注册节点资源监听事件,动态维护graph node.AddGraphEventHandlers( graph, config.VersionedInformerFactory.Core().V1().Nodes(), config.VersionedInformerFactory.Core().V1().Pods(), config.VersionedInformerFactory.Core().V1().PersistentVolumes(), config.VersionedInformerFactory.Storage().V1().VolumeAttachments(), ) // 初始化node鉴权对象 nodeAuthorizer := node.NewAuthorizer(graph, nodeidentifier.NewDefaultNodeIdentifier(), bootstrappolicy.NodeRules()) authorizers = append(authorizers, nodeAuthorizer) ruleResolvers = append(ruleResolvers, nodeAuthorizer) case modes.ModeAlwaysAllow: alwaysAllowAuthorizer := authorizerfactory.NewAlwaysAllowAuthorizer() authorizers = append(authorizers, alwaysAllowAuthorizer) ruleResolvers = append(ruleResolvers, alwaysAllowAuthorizer) case modes.ModeAlwaysDeny: alwaysDenyAuthorizer := authorizerfactory.NewAlwaysDenyAuthorizer() authorizers = append(authorizers, alwaysDenyAuthorizer) ruleResolvers = append(ruleResolvers, alwaysDenyAuthorizer) case modes.ModeABAC: // 加载ABAC权限策略 abacAuthorizer, err := abac.NewFromFile(config.PolicyFile) ... authorizers = append(authorizers, abacAuthorizer) ruleResolvers = append(ruleResolvers, abacAuthorizer) case modes.ModeWebhook: ... // 初始化webook authorizer webhookAuthorizer, err := webhook.New(clientConfig, config.WebhookVersion, config.WebhookCacheAuthorizedTTL, config.WebhookCacheUnauthorizedTTL, *config.WebhookRetryBackoff, ) ... authorizers = append(authorizers, webhookAuthorizer) ruleResolvers = append(ruleResolvers, webhookAuthorizer) case modes.ModeRBAC: // 初始化RBAC鉴权对象 rbacAuthorizer := rbac.New( &rbac.RoleGetter{Lister: config.VersionedInformerFactory.Rbac().V1().Roles().Lister()}, &rbac.RoleBindingLister{Lister: config.VersionedInformerFactory.Rbac().V1().RoleBindings().Lister()}, &rbac.ClusterRoleGetter{Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoles().Lister()}, &rbac.ClusterRoleBindingLister{Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoleBindings().Lister()}, ) authorizers = append(authorizers, rbacAuthorizer) ruleResolvers = append(ruleResolvers, rbacAuthorizer) default: return nil, nil, fmt.Errorf("unknown authorization mode %s specified", authorizationMode) } } // 转为联合鉴权器及联合规则解析器 return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil } // Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful. func (authzHandler unionAuthzHandler) Authorize(ctx context.Context, a authorizer.Attributes) (...) { ... // 遍历鉴权器 for _, currAuthzHandler := range authzHandler { // 执行鉴权 decision, reason, err := currAuthzHandler.Authorize(ctx, a) ... switch decision { // 明确allow/deny case authorizer.DecisionAllow, authorizer.DecisionDeny: return decision, reason, err // pass case authorizer.DecisionNoOpinion: // continue to the next authorizer } } return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist) } // RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful. func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) (...) { ... // 遍历规则解析器 for _, currAuthzHandler := range authzHandler { // 解析规则 resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace) // 规则不确定标记 if incomplete { incompleteStatus = true } ... // 资源规则 if len(resourceRules) > 0 { resourceRulesList = append(resourceRulesList, resourceRules...) } // 非资源规则 if len(nonResourceRules) > 0 { nonResourceRulesList = append(nonResourceRulesList, nonResourceRules...) } } return resourceRulesList, nonResourceRulesList, incompleteStatus, utilerrors.NewAggregate(errList) }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注意
Authorizator根据配置加载,默认开启node和rbac鉴权
# 2.3.withAuthz
authorizator注册位于DefaultBuildHandlerChain,内层包装director,外层由其它chain handler包装,作为chain的一部分。// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise. func WithAuthorization(...) http.Handler { ... return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() // 获取客户端请求属性(资源属性/用户) attributes, err := GetAuthorizerAttributes(ctx) ... // union authorizator鉴权 authorized, reason, err := a.Authorize(ctx, attributes) // 鉴权通过 if authorized == authorizer.DecisionAllow { ... // 转交给director handler.ServeHTTP(w, req) return } ... // 拒绝 responsewriters.Forbidden(ctx, attributes, w, req, reason, s) }) } // Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful. func (authzHandler unionAuthzHandler) Authorize(...) (authorizer.Decision, string, error) { ... // 遍历鉴权器 for _, currAuthzHandler := range authzHandler { // 执行鉴权 decision, reason, err := currAuthzHandler.Authorize(ctx, a) ... // 结果处理 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) }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注意
union authorizator其实也实现了Authorizer接口,作为鉴权模块外的总调入口
# 3.鉴权
# 3.1.node
node鉴权专为kubelet组件设计,是一种特殊的RBAC鉴权模式,通过为system:node用户授权实现,分为特定资源鉴权与通用鉴权两部分。// NewAuthorizer returns a new node authorizer func NewAuthorizer(...) *NodeAuthorizer { return &NodeAuthorizer{ graph: graph, // 资源引用图 identifier: identifier, // 节点标识 nodeRules: rules, // 静态资源白名单(node/pod/pv/pvc/...) features: utilfeature.DefaultFeatureGate, } } func (r *NodeAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attributes) (...) { // 获取节点(system:node:name用户 system:nodes组) nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser()) ... // 敏感资源请求(限制verb---限制名字---限制资源关联) if attrs.IsResourceRequest() { // 构建请求资源类型 requestResource := schema.GroupResource{Group: attrs.GetAPIGroup(), Resource: attrs.GetResource()} switch requestResource { case secretResource: // secret检查 return r.authorizeReadNamespacedObject(nodeName, secretVertexType, attrs) case configMapResource: // cm检查 return r.authorizeReadNamespacedObject(nodeName, configMapVertexType, attrs) case pvcResource: // pvc检查 if attrs.GetSubresource() == "status" { return r.authorizeStatusUpdate(nodeName, pvcVertexType, attrs) } return r.authorizeGet(nodeName, pvcVertexType, attrs) case pvResource: // pv检查 return r.authorizeGet(nodeName, pvVertexType, attrs) case vaResource: // va检查 return r.authorizeGet(nodeName, vaVertexType, attrs) case svcAcctResource: // svc检查 return r.authorizeCreateToken(nodeName, serviceAccountVertexType, attrs) case leaseResource: // lease检查 return r.authorizeLease(nodeName, attrs) case csiNodeResource: // csinode检查 return r.authorizeCSINode(nodeName, attrs) } } // 其它资源走静态规则 if rbac.RulesAllow(attrs, r.nodeRules...) { return authorizer.DecisionAllow, "", nil } return authorizer.DecisionNoOpinion, "", nil } func RulesAllow(requestAttributes authorizer.Attributes, rules ...rbacv1.PolicyRule) bool { for i := range rules { // 基于静态rule匹配 if RuleAllows(requestAttributes, &rules[i]) { return true } } return false }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注意
nodeAuthorizer本质上是基于内置的rules规则进行鉴权,敏感资源基于固定rule检查verb/name/graph信息,其它资源基于白名单检查
# 3.2.webhook
webhook鉴权器依赖集群外部的鉴权服务,鉴权请求会基于Post发送给外部的鉴权服务,webhook调用配置基于apiserver启动配置指定。// New creates a new WebhookAuthorizer from the provided kubeconfig file. func New(...) (*WebhookAuthorizer, error) { // webhook调用客户端 subjectAccessReview, err := subjectAccessReviewInterfaceFromConfig(config, version, retryBackoff) ... return newWithBackoff(subjectAccessReview, authorizedTTL, unauthorizedTTL, retryBackoff...) } // newWithBackoff allows tests to skip the sleep. func newWithBackoff(...) (*WebhookAuthorizer, error) { return &WebhookAuthorizer{ subjectAccessReview: subjectAccessReview, responseCache: cache.NewLRUExpireCache(8192), authorizedTTL: authorizedTTL, unauthorizedTTL: unauthorizedTTL, retryBackoff: retryBackoff, decisionOnError: authorizer.DecisionNoOpinion, metrics: metrics, }, nil } // Authorize makes a REST request to the remote service describing the attempted action as a JSON serialized. func (w *WebhookAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (...) { r := &authorizationv1.SubjectAccessReview{} // 初始化访问用户 if user := attr.GetUser(); user != nil { r.Spec = authorizationv1.SubjectAccessReviewSpec{ User: user.GetName(), UID: user.GetUID(), Groups: user.GetGroups(), Extra: convertToSARExtra(user.GetExtra()), } } // 初始化访问资源 if attr.IsResourceRequest() { r.Spec.ResourceAttributes = &authorizationv1.ResourceAttributes{ Namespace: attr.GetNamespace(), Verb: attr.GetVerb(), Group: attr.GetAPIGroup(), Version: attr.GetAPIVersion(), Resource: attr.GetResource(), Subresource: attr.GetSubresource(), Name: attr.GetName(), } } else { r.Spec.NonResourceAttributes = &authorizationv1.NonResourceAttributes{ Path: attr.GetPath(), Verb: attr.GetVerb(), } } // 序列化 key, err := json.Marshal(r.Spec) ... // 本地缓存存在,不再发起请求 if entry, ok := w.responseCache.Get(string(key)); ok { r.Status = entry.(authorizationv1.SubjectAccessReviewStatus) } else { ... // 发起post请求 if err := webhook.WithExponentialBackoff(ctx, w.retryBackoff, func() error { ... result, statusCode, sarErr = w.subjectAccessReview.Create(ctx, r, metav1.CreateOptions{}) ... return sarErr }, webhook.DefaultShouldRetry) ... // 结果 r.Status = result.Status // 长度未超出10000缓存结果 if shouldCache(attr) { if r.Status.Allowed { w.responseCache.Add(string(key), r.Status, w.authorizedTTL) } else { w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL) } } } // 鉴权结果 switch { case r.Status.Denied && r.Status.Allowed: return authorizer.DecisionDeny, r.Status.Reason, fmt.Errorf("webhook subject access review...") case r.Status.Denied: return authorizer.DecisionDeny, r.Status.Reason, nil case r.Status.Allowed: return authorizer.DecisionAllow, r.Status.Reason, nil default: return authorizer.DecisionNoOpinion, r.Status.Reason, nil } }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注意
webhook鉴权与认证流程类似,都是基于Post请求将鉴权委托给外部webhook服务
# 3.3.ABAC
ABAC定义了访问的控制范围,通过属性组合策略向用户授予访问权限,具体的鉴权预设策略需关联json文件加载,是一种静态的权限配置方式。// NewFromFile attempts to create a policy list from the given file. func NewFromFile(path string) (PolicyList, error) { // 加载配置文件 file, err := os.Open(path) ... defer file.Close() ... // 循环读取各行 for scanner.Scan() { ... // 读取行内容(每行就是一个Policy) b := scanner.Bytes() // 忽略空行或注释行 trimmed := strings.TrimSpace(string(b)) if len(trimmed) == 0 || strings.HasPrefix(trimmed, "#") { continue } // 解码为Policy对象 decodedObj, _, err := decoder.Decode(b, nil, nil) ... // 检查类型合法性 decodedPolicy, ok := decodedObj.(*abac.Policy) ... // 记录 pl = append(pl, decodedPolicy) } ... return pl, nil } // Authorize implements authorizer.Authorize func (pl PolicyList) Authorize(ctx context.Context, a authorizer.Attributes) (...) { // 基于静态策略匹配 for _, p := range pl { if matches(*p, a) { return authorizer.DecisionAllow, "", nil } } return authorizer.DecisionNoOpinion, "No policy matched.", nil } func matches(p abac.Policy, a authorizer.Attributes) bool { // 匹配user/group if subjectMatches(p, a.GetUser()) { // 匹配操作规则(只读请求放行/写入请求要求策略未标记只读) if verbMatches(p, a) { // namespace/resource/apigroup匹配 if resourceMatches(p, a) { return true } // 非资源请求的路径匹配(/api /api/*) if nonResourceMatches(p, a) { return true } } } return false }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注意
ABAC策略基于静态配置文件,已逐渐被RBAC动态策略取代
# 3.4.RBAC
RBAC基于角色调节资源访问,通过role/clusterrole描述具体的资源权限策略,利用rolebinding/clusterrolebinding将策略绑定到用户。func New(...) *RBACAuthorizer { // 初始化鉴权器(role/roleBinding/clusterRole/clusterRoleBinding的Lister获取器) authorizer := &RBACAuthorizer{ authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver( roles, roleBindings, clusterRoles, clusterRoleBindings, ), } return authorizer } // 鉴权 func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (...) { // 构造authorizingVisitor,记录匹配结果 ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes} // 遍历用户相关RBAC规则进行匹配(verb/group/resource/resourceName) r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit) // 鉴权通过 if ruleCheckingVisitor.allowed { return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil } ... // 无法检验,PASS return authorizer.DecisionNoOpinion, reason, nil } // 最终会执行到这里进行规则获取及匹配 func (r *DefaultRuleResolver) VisitRulesFor(...) bool) { // 获取clusterRoleBinding对象 clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings() ... sourceDescriber := &clusterRoleBindingDescriber{} // 遍历clusterRoleBinding for _, clusterRoleBinding := range clusterRoleBindings { // 匹配授权用户 subjectIndex, applies := appliesTo(user, clusterRoleBinding.Subjects, "") ... // 获取关联的clusterRole rules rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") ... sourceDescriber.binding = clusterRoleBinding sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex] // 匹配规则 for i := range rules { if !visitor(sourceDescriber, &rules[i], nil) { return } } } // clusterRoleBinding未匹配,基于ns级别的roleBinding匹配 if len(namespace) > 0 { // 获取roleBinding对象 roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace) ... sourceDescriber := &roleBindingDescriber{} // 遍历roleBind对象 for _, roleBinding := range roleBindings { // 匹配用户 subjectIndex, applies := appliesTo(user, roleBinding.Subjects, namespace) ... // 获取role rules规则 rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace) ... sourceDescriber.binding = roleBinding sourceDescriber.subject = &roleBinding.Subjects[subjectIndex] // 匹配规则 for i := range rules { if !visitor(sourceDescriber, &rules[i], nil) { return } } } } }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注意
RBAC鉴权基于API资源记录关联用户的资源权限,通过user/verb/group/resource/resourceName对比完成鉴权