在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
中。
该函数的鉴权操作步骤如下:
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分支。
本文转自SegmentFault-Kubernetes RBAC源码解析