在kubernetes 1.6版本中,正式引入了角色訪問控制機制(Role-Based Access Control,RBAC),讓集羣管理員能夠針對使用者(user或者group)或服務帳號(service account),進行更精確的資源訪問控制。api
在正式對kubernetes RBAC的源碼進行解析以前,須要瞭解幾個基本的概念。app
角色:是一系列權限的集合,例如一個角色包含services的list、watch權限。ide
角色綁定:是把角色映射到用戶,從而讓這些用戶繼承角色的權限。函數
若需在kubernetes中開啓RBAC的服務,在apiserver的啓動參數裏設定鑑權模式,ui
--authorization-mode=RBAC
lua
kubernetes中角色分爲Role和ClusterRole,Role是namespace級別的,ClusterRole是集羣級別的。spa
與RBAC相關的結構體的定義,所有位於pkg/apis/rbac/types.go
文件中。code
ClusterRole的結構體:server
type ClusterRole struct { metav1.TypeMeta metav1.ObjectMeta Rules []PolicyRule AggregationRule *AggregationRule }
Role的結構體:對象
type Role struct { metav1.TypeMeta metav1.ObjectMeta Rules []PolicyRule }
ClusterRole與Role的結構體定義基本是相似的,角色裏面都是關聯的Rules規則,一個角色有哪些權限,經過Rules去定義。下面是Rule的結構體定義,主要控制訪問的資源、訪問URL的限制。
type PolicyRule struct { Verbs []string APIGroups []string Resources []string ResourceNames []string NonResourceURLs []string }
那麼角色是怎麼和使用者或者服務帳號綁定的呢?這就要看ClusterRoleBinding和RoleBinding。RoleBinding是把角色在namespace中對資源的權限受權給使用者或服務帳號;ClusterRoleBinding容許使用者或服務帳號在整個集羣中的受權訪問。ClusterRoleBinding與RoleBinding的功能是一致的,只是有着更寬的使用範圍。下面是ClusterRoleBinding的結構體:
type ClusterRoleBinding struct { metav1.TypeMeta metav1.ObjectMeta Subjects []Subject RoleRef RoleRef }
這是與ClusterRoleBinding具備相同屬性的結構體RoleBinding:
type RoleBinding struct { metav1.TypeMeta metav1.ObjectMeta Subjects []Subject RoleRef RoleRef }
這兩個結構體主要看兩個屬性值,第一個是Subjects,它是綁定的對象,包括User、Group、ServiceAccount;第二個是RoleRef,它是綁定的角色。
在瞭解了kubernetes中角色的定義,並掌握瞭如何將角色中定義的資源的訪問權限賦予給User、Group、ServiceAccount以後,咱們須要瞭解的是,在處理一個API請求時,如何對該請求進行鑑權的處理?
在kubernetes中,全部的請求都會經由apiserver進行處理。在初始化apiserver時,若指定了鑑權模式包括了RBAC後,將會註冊一個RBAC的Handler模塊。這樣,在apiserver接收請求並處理時,將會調用該Handler,來判斷該請求的調用者是否有權限請求該資源。
該Handler位於staging/src/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
文件中:
func WithAuthorization(handler http.Handler, requestContextMapper request.RequestContextMapper, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler { if a == nil { glog.Warningf("Authorization is disabled") return handler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx, ok := requestContextMapper.Get(req) if !ok { responsewriters.InternalError(w, req, errors.New("no context found for request")) return } attributes, err := GetAuthorizerAttributes(ctx) if err != nil { responsewriters.InternalError(w, req, err) return } authorized, reason, err := a.Authorize(attributes) // 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 { handler.ServeHTTP(w, req) return } if err != nil { responsewriters.InternalError(w, req, err) return } glog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason) responsewriters.Forbidden(ctx, attributes, w, req, reason, s) }) }
該Handler作了兩件事,一是根據http request提取出鑑權所需的信息,經過函數GetAuthorizerAttributes()
實現,二是根據提取出的信息,執行鑑權的核心操做,去判斷請求的調用者是否有權限操做相關資源,經過函數Authorize()
處理。
提取信息的函數GetAuthorizerAttributes()
位於staging/src/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
文件中。主要包括請求的APIGroup、APIVersion、Resource、SubResource、Verbs、Namespace等這些在PolicyRule結構體中定義的信息。
func GetAuthorizerAttributes(ctx request.Context) (authorizer.Attributes, error) { attribs := authorizer.AttributesRecord{} user, ok := request.UserFrom(ctx) if ok { attribs.User = user } requestInfo, found := request.RequestInfoFrom(ctx) if !found { return nil, errors.New("no RequestInfo found in the context") } // Start with common attributes that apply to resource and non-resource requests attribs.ResourceRequest = requestInfo.IsResourceRequest attribs.Path = requestInfo.Path attribs.Verb = requestInfo.Verb attribs.APIGroup = requestInfo.APIGroup attribs.APIVersion = requestInfo.APIVersion attribs.Resource = requestInfo.Resource attribs.Subresource = requestInfo.Subresource attribs.Namespace = requestInfo.Namespace attribs.Name = requestInfo.Name return &attribs, nil }
在獲取了鑑權所需的相關信息後,kubernetes須要根據這些信息去執行鑑權的核心操做。鑑權的函數Authorize()
位於文件plugin/pkg/auth/authorizer/rbac/rbac.go
文件中。
該函數會調用VisitRulesFor()
來進行鑑權的最後判斷工做。
func (r *RBACAuthorizer) Authorize(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 glog.V(5) { 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" } glog.Infof("RBAC DENY: user %q groups %q cannot %s %s", requestAttributes.GetUser().GetName(), requestAttributes.GetUser().GetGroups(), operation, scope) } reason := "" if len(ruleCheckingVisitor.errors) > 0 { reason = fmt.Sprintf("%v", utilerrors.NewAggregate(ruleCheckingVisitor.errors)) } return authorizer.DecisionNoOpinion, reason, nil }
VisitRulesFor()
位於文件pkg/registry/rbac/validation/rule.go
中。
該函數的鑑權操做步驟以下:
GetRoleReferenceRules()
獲取綁定的Role所控制的訪問的資源;func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbac.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 } rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") if err != nil { if !visitor(nil, nil, err) { return } continue } 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 } } } } } } // GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding. func (r *DefaultRuleResolver) GetRoleReferenceRules(roleRef rbac.RoleRef, bindingNamespace string) ([]rbac.PolicyRule, error) { switch kind := rbac.RoleRefGroupKind(roleRef); kind { case rbac.Kind("Role"): role, err := r.roleGetter.GetRole(bindingNamespace, roleRef.Name) if err != nil { return nil, err } return role.Rules, nil case rbac.Kind("ClusterRole"): clusterRole, err := r.clusterRoleGetter.GetClusterRole(roleRef.Name) if err != nil { return nil, err } return clusterRole.Rules, nil default: return nil, fmt.Errorf("unsupported role reference kind: %q", kind) } }
本文全部源碼均來自於kubernetes release-1.10分支。