天天看點

Kubernetes RBAC源碼解析

RBAC基礎概念

在kubernetes 1.6版本中,正式引入了角色通路控制機制(Role-Based Access Control,RBAC),讓叢集管理者可以針對使用者(user或者group)或服務賬号(service account),進行更精确的資源通路控制。

在正式對kubernetes RBAC的源碼進行解析之前,需要了 解幾個基本的概念。

角色:是一系列權限的集合,例如一個角色包含services的list、watch權限。

角色綁定:是把角色映射到使用者,進而讓這些使用者繼承角色的權限。

若需在kubernetes中開啟RBAC的服務,在apiserver的啟動參數裡設定鑒權模式,

--authorization-mode=RBAC

角色與角色綁定

kubernetes中角色分為Role和ClusterRole,Role是namespace級别的,ClusterRole是叢集級别的。

與RBAC相關的結構體的定義,全部位于

pkg/apis/rbac/types.go

檔案中。

ClusterRole的結構體:

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

中。

該函數的鑒權操作步驟如下:

  1. 擷取所有的ClusterRoleBindings,并對其進行周遊操作;
  2. 根據請求調用者的資訊,判斷該調用者是否被綁定在該ClusterRoleBinding中;
  3. 若綁定在該ClusterRoleBinding中,将通過函數

    GetRoleReferenceRules()

    擷取綁定的Role所控制的通路的資源;
  4. 将Role所控制的通路的資源,與從API請求中提取出的資源進行比對,若比對成功,即為API請求的調用者有權通路相關資源;
  5. 若在所有的ClusterRoleBinding中,都沒有獲得鑒權成功的操作,将會判斷提取出的資訊中是否包括了namespace的資訊,若包括了,将會擷取該namespace下的所有RoleBindings。
  6. 擷取了該namesapce下的所有RoleBindings之後,所執行的操作将與ClusterRoleBinding類似,對其進行周遊,擷取對應Role控制的通路的資源,與從API請求中提取的資源資訊進行比對。
  7. 若在周遊了所有CluterRoleBindings,及該namespace下的所有RoleBingdings之後,仍沒有對資源比對成功,則可判斷該API請求的調用者沒有權限通路相關資源。
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分支。

本文轉自SegmentFault-

Kubernetes RBAC源碼解析

繼續閱讀