资讯专栏INFORMATION COLUMN

Kubernetes RBAC源码解析

2json / 2576人阅读

摘要:源码解析基础概念在版本中,正式引入了角色访问控制机制,,让集群管理员可以针对使用者或者或服务账号,进行更精确的资源访问控制。在正式对的源码进行解析之前,需要了解几个基本的概念。备注本文所有源码均来自于分支。

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中。

该函数的鉴权操作步骤如下:

获取所有的ClusterRoleBindings,并对其进行遍历操作;

根据请求调用者的信息,判断该调用者是否被绑定在该ClusterRoleBinding中;

若绑定在该ClusterRoleBinding中,将通过函数GetRoleReferenceRules()获取绑定的Role所控制的访问的资源;

将Role所控制的访问的资源,与从API请求中提取出的资源进行比对,若比对成功,即为API请求的调用者有权访问相关资源;

若在所有的ClusterRoleBinding中,都没有获得鉴权成功的操作,将会判断提取出的信息中是否包括了namespace的信息,若包括了,将会获取该namespace下的所有RoleBindings。

获取了该namesapce下的所有RoleBindings之后,所执行的操作将与ClusterRoleBinding类似,对其进行遍历,获取对应Role控制的访问的资源,与从API请求中提取的资源信息进行比对。

若在遍历了所有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分支。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/32704.html

相关文章

  • coredns 排错记

    摘要:如果新服务器无法启动,则初始服务器实例仍然可用且仍然提供查询,但处理程序保持关闭状态。在成功重新加载或完全重新启动之前,运行状况不会回复请求。后记在新创建后更新有问题需要解决 核心链接 https://kubernetes.io/docs/ta... CoreDNS 安装 apiVersion: v1 kind: ServiceAccount metadata: name: cor...

    Salamander 评论0 收藏0
  • Rancher中的K8S认证和RBAC

    摘要:在中使用验证使用身份验证策略来认证用户的。审阅状态包含名称和组等用户信息。中的授权模块稍后将以此确定该用户的访问级别。认证请求认证服务决定该用户是否通过认证,并向发送响应。在对的请求成功进行认证之后,必须授权该请求。 Rancher Kubernetes拥有RBAC(基于角色的访问控制)功能,此功能可以让管理员配置不同的策略,允许或拒绝用户和服务帐户访问Kubernetes API资源...

    raise_yang 评论0 收藏0
  • Rancher中的K8S认证和RBAC

    摘要:在中使用验证使用身份验证策略来认证用户的。审阅状态包含名称和组等用户信息。中的授权模块稍后将以此确定该用户的访问级别。认证请求认证服务决定该用户是否通过认证,并向发送响应。在对的请求成功进行认证之后,必须授权该请求。 Rancher Kubernetes拥有RBAC(基于角色的访问控制)功能,此功能可以让管理员配置不同的策略,允许或拒绝用户和服务帐户访问Kubernetes API资源...

    Forest10 评论0 收藏0
  • Kubernetes学习视频教程

    摘要:视频学习目录核心要点及架构概述基础概念初始化应用快速入门资源清单定义入门控制器应用进阶控制器应用进阶控制器控制器资源与存储卷和控制器认证及认证及分级授权配置网络插件基于的网络策略调度器预选策略及优选函 Kubernetes视频学习目录 01-Devops核心要点及kubernetes架构概述.mp4 02-kubernetes基础概念.mp4 03-kubeadm初始化kuberne...

    wslongchen 评论0 收藏0
  • Kubernetes安全三步谈:如何通过RBAC和强身份验证确保外部安全

    摘要:本文将介绍通过强身份验证如何确保企业的集群免受外部攻击。服务器虽然面向公开,但是受到证书身份验证的保护。年年底被爆出的首个严重安全漏洞,就是由联合创始人及首席架构师发现的。 毋庸置疑,K8s已经成为云容器编排系统的标准,但是,如果缺乏K8s环境相关的安全问题认识的话,会致使各种组件暴露在网络集群内外的攻击之下。本文将介绍通过强身份验证如何确保企业的K8s集群免受外部攻击。 showIm...

    _DangJin 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<