Kubernetes应用系统API用户认证与鉴权 -- 鉴权篇

SuKai February 14, 2022

上篇文章讲了通过LDAP和Dex实现两种不同的API用户认证的方式,用户认证是识别API请求的来源,让系统知道请求的访问者是谁。应用系统可以根据请求的身份,来判断该用户可以访问哪些API接口获得数据,这就是用户鉴权。下面我们一起来看一下,在Kubernetes平台上,如何通过RBAC来实现资源访问限制,同样我们还是通过Kubesphere的代码来学习。

鉴权实现的过程:

1,构建一个HandlerChain,HandlerChain中包含了:WithAuthentication, WithRequestInfo和WithAuthorization用户认证和鉴权的三个Filter以及WithAuditing Filter用于审计日志。Filter可以理解为web 框架的Middleware。

2,filters.WithAuthentication上篇文章介绍过,完成用户身份识别和分发Token,将用户信息写入Context。

3,filters.WithRequestInfo处理API请求信息,将请求信息保存到context中。请求信息包括:Path, Verb, APIPrefix, APIGroup, APIVersion, Namespace, Resource, Name这些RBAC鉴权需要用到的资源信息,以及客户端信息等。

4,filters.WithAuthorization根据RequestInfo和RBAC规则,判断请求的合法性,终止或者允许请求继续下去。

代码实现:

1,HandlerChain处理请求

这里可以看到有两个鉴权,一个是pathAuthorizer,一个是RBACAuthorizer,pathAuthorizer定义了哪些URL Path不需要鉴权,RBACAuthorizer顾名思义就是通过RBAC鉴权,后面我们详细介绍这两个鉴权。

var authorizers authorizer.Authorizer

switch s.Config.AuthorizationOptions.Mode {
case authorization.AlwaysAllow:
   authorizers = authorizerfactory.NewAlwaysAllowAuthorizer()
case authorization.AlwaysDeny:
   authorizers = authorizerfactory.NewAlwaysDenyAuthorizer()
default:
   fallthrough
case authorization.RBAC:
   excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*", "/kapis/version", "/kapis/metrics"}
   pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
   amOperator := am.NewReadOnlyOperator(s.InformerFactory, s.DevopsClient)
   authorizers = unionauthorizer.New(pathAuthorizer, rbac.NewRBACAuthorizer(amOperator))
}

handler = filters.WithAuthorization(handler, authorizers)

handler = filters.WithAuthentication(handler, authn)
handler = filters.WithRequestInfo(handler, requestInfoResolver)

s.Server.Handler = handler

2,WithAuthentication身份认证

authRequest.AuthenticateRequest认证完成后, req.WithContext(request.WithUser(req.Context(), resp.User))将用户信息写入request的context。

func WithAuthentication(handler http.Handler, authRequest authenticator.Request) http.Handler {

   return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
      resp, ok, err := authRequest.AuthenticateRequest(req)


      req = req.WithContext(request.WithUser(req.Context(), resp.User))
      handler.ServeHTTP(w, req)
   })
}
// WithUser returns a copy of parent in which the user value is set
func WithUser(parent context.Context, user user.Info) context.Context {
	return WithValue(parent, userKey, user)
}
		
func WithValue(parent context.Context, key interface{}, val interface{}) context.Context {
   return context.WithValue(parent, key, val)
}

3,WithRequestInfo处理请求信息

ctx获取request的context,resolver.NewRequestInfo解析API请求生成请求信息,req.WithContext(request.WithRequestInfo(ctx, info))将请求信息写入ctx,并保存到request的context中。

func WithRequestInfo(handler http.Handler, resolver request.RequestInfoResolver) http.Handler {
   return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

      ctx := req.Context()
      info, err := resolver.NewRequestInfo(req)


      req = req.WithContext(request.WithRequestInfo(ctx, info))
      handler.ServeHTTP(w, req)
   })
}

NewRequestInfo解析请求信息

从URL中获取,APIPrefix, APIGroup, APIVersion, Resource, Name等资源信息以及 Cluster, Workspace, Namespace, Devops等请求范围信息。

根据API请求方法,获取资源操作的动作类型,POST为create操作,GET为get操作, PUT为update操作等。

根据API Query参数,判断是否为watch操作。

func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) {
   requestInfo := RequestInfo{
      IsKubernetesRequest: false,
      RequestInfo: &k8srequest.RequestInfo{
         Path: req.URL.Path,
         Verb: req.Method,
      },
      Workspace: api.WorkspaceNone,
      Cluster:   api.ClusterNone,
      SourceIP:  iputil.RemoteIp(req),
      UserAgent: req.UserAgent(),
   }

   defer func() {
      prefix := requestInfo.APIPrefix
      if prefix == "" {
         currentParts := splitPath(requestInfo.Path)
         //Proxy discovery API
         if len(currentParts) > 0 && len(currentParts) < 3 {
            prefix = currentParts[0]
         }
      }
      if kubernetesAPIPrefixes.Has(prefix) {
         requestInfo.IsKubernetesRequest = true
      }
   }()

   currentParts := splitPath(req.URL.Path)
   if len(currentParts) < 3 {
      return &requestInfo, nil
   }

   if !r.APIPrefixes.Has(currentParts[0]) {
      // return a non-resource request
      return &requestInfo, nil
   }
   requestInfo.APIPrefix = currentParts[0]
   currentParts = currentParts[1:]

   // URL forms: /clusters/{cluster}/*
   if currentParts[0] == "clusters" {
      if len(currentParts) > 1 {
         requestInfo.Cluster = currentParts[1]
      }
      if len(currentParts) > 2 {
         currentParts = currentParts[2:]
      }
   }

   if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
      // one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
      if len(currentParts) < 3 {
         // return a non-resource request
         return &requestInfo, nil
      }

      requestInfo.APIGroup = currentParts[0]
      currentParts = currentParts[1:]
   }

   requestInfo.IsResourceRequest = true
   requestInfo.APIVersion = currentParts[0]
   currentParts = currentParts[1:]

   if len(currentParts) > 0 && specialVerbs.Has(currentParts[0]) {
      if len(currentParts) < 2 {
         return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url: %v", req.URL)
      }

      requestInfo.Verb = currentParts[0]
      currentParts = currentParts[1:]
   } else {
      switch req.Method {
      case "POST":
         requestInfo.Verb = "create"
      case "GET", "HEAD":
         requestInfo.Verb = "get"
      case "PUT":
         requestInfo.Verb = "update"
      case "PATCH":
         requestInfo.Verb = "patch"
      case "DELETE":
         requestInfo.Verb = "delete"
      default:
         requestInfo.Verb = ""
      }
   }

   // URL forms: /workspaces/{workspace}/*
   if currentParts[0] == "workspaces" {
      if len(currentParts) > 1 {
         requestInfo.Workspace = currentParts[1]
      }
      if len(currentParts) > 2 {
         currentParts = currentParts[2:]
      }
   }

   // URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
   if currentParts[0] == "namespaces" {
      if len(currentParts) > 1 {
         requestInfo.Namespace = currentParts[1]

         // if there is another step after the namespace name and it is not a known namespace subresource
         // move currentParts to include it as a resource in its own right
         if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) {
            currentParts = currentParts[2:]
         }
      }
   } else if currentParts[0] == "devops" {
      if len(currentParts) > 1 {
         requestInfo.DevOps = currentParts[1]

         // if there is another step after the devops name
         // move currentParts to include it as a resource in its own right
         if len(currentParts) > 2 {
            currentParts = currentParts[2:]
         }
      }
   } else {
      requestInfo.Namespace = metav1.NamespaceNone
      requestInfo.DevOps = metav1.NamespaceNone
   }

   // parsing successful, so we now know the proper value for .Parts
   requestInfo.Parts = currentParts

   requestInfo.ResourceScope = r.resolveResourceScope(requestInfo)

   // parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
   switch {
   case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb):
      requestInfo.Subresource = requestInfo.Parts[2]
      fallthrough
   case len(requestInfo.Parts) >= 2:
      requestInfo.Name = requestInfo.Parts[1]
      fallthrough
   case len(requestInfo.Parts) >= 1:
      requestInfo.Resource = requestInfo.Parts[0]
   }

   // if there's no name on the request and we thought it was a get before, then the actual verb is a list or a watch
   if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
      opts := metainternalversion.ListOptions{}
      if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, &opts); err != nil {
         // An error in parsing request will result in default to "list" and not setting "name" field.
         klog.Errorf("Couldn't parse request %#v: %v", req.URL.Query(), err)
         // Reset opts to not rely on partial results from parsing.
         // However, if watch is set, let's report it.
         opts = metainternalversion.ListOptions{}
         if values := req.URL.Query()["watch"]; len(values) > 0 {
            switch strings.ToLower(values[0]) {
            case "false", "0":
            default:
               opts.Watch = true
            }
         }
      }

      if opts.Watch {
         requestInfo.Verb = "watch"
      } else {
         requestInfo.Verb = "list"
      }

      if opts.FieldSelector != nil {
         if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok {
            if len(path.IsValidPathSegmentName(name)) == 0 {
               requestInfo.Name = name
            }
         }
      }
   }

   // URL forms: /api/v1/watch/namespaces?labelSelector=kubesphere.io/workspace=system-workspace
   if requestInfo.Verb == "watch" {
      selector := req.URL.Query().Get("labelSelector")
      if strings.HasPrefix(selector, workspaceSelectorPrefix) {
         workspace := strings.TrimPrefix(selector, workspaceSelectorPrefix)
         requestInfo.Workspace = workspace
         requestInfo.ResourceScope = WorkspaceScope
      }
   }

   // if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection
   if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" {
      requestInfo.Verb = "deletecollection"
   }

   return &requestInfo, nil
}

4,WithAuthorization鉴权

getAuthorizerAttributes从request.context中获取请求信息和用户信息,authorizers.Authorize根据请求信息和用户信息进行权限认证,鉴权为DecisionAllow的继续

func WithAuthorization(handler http.Handler, authorizers authorizer.Authorizer) http.Handler {

   return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
      ctx := req.Context()

      attributes, err := getAuthorizerAttributes(ctx)

      authorized, reason, err := authorizers.Authorize(attributes)
      if authorized == authorizer.DecisionAllow {
         handler.ServeHTTP(w, req)
         return
      }

      klog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
      responsewriters.Forbidden(ctx, attributes, w, req, reason, defaultSerializer)
   })
}

getAuthorizerAttributes获取上面WithRequestInfo写入到request.context中的信息,以及WithAuthentication写入的用户信息。

func getAuthorizerAttributes(ctx context.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.ResourceScope = requestInfo.ResourceScope
   attribs.ResourceRequest = requestInfo.IsResourceRequest
   attribs.Path = requestInfo.Path
   attribs.Verb = requestInfo.Verb
   attribs.Cluster = requestInfo.Cluster
   attribs.Workspace = requestInfo.Workspace
   attribs.KubernetesRequest = requestInfo.IsKubernetesRequest
   attribs.APIGroup = requestInfo.APIGroup
   attribs.APIVersion = requestInfo.APIVersion
   attribs.Resource = requestInfo.Resource
   attribs.Subresource = requestInfo.Subresource
   attribs.Namespace = requestInfo.Namespace
   attribs.DevOps = requestInfo.DevOps
   attribs.Name = requestInfo.Name

   return &attribs, nil
}


// UserFrom returns the value of the user key on the ctx
func UserFrom(ctx context.Context) (user.Info, bool) {
	user, ok := ctx.Value(userKey).(user.Info)
	return user, ok
}

func RequestInfoFrom(ctx context.Context) (*RequestInfo, bool) {
	info, ok := ctx.Value(requestInfoKey).(*RequestInfo)
	return info, ok
}

authorizers.Authorize

在上面unionauthorizer.New(pathAuthorizer, rbac.NewRBACAuthorizer(amOperator))返回的是一个接口,New中的参数数组也就是一个authorizer接口数组,unionAuthzHandler实现了这个接口,unionAuthzHandler实现了这个接口的方法Authorize,unionAuthzHandler.Authorize调用数组中的authorize验证请求权限,最终返回是Allow还是Deny。

func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
   var (
      errlist    []error
      reasonlist []string
   )

   for _, currAuthzHandler := range authzHandler {
      decision, reason, err := currAuthzHandler.Authorize(a)

      if err != nil {
         errlist = append(errlist, err)
      }
      if len(reason) != 0 {
         reasonlist = append(reasonlist, reason)
      }
      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)
}

下面具体看一下两个Authorizer权限验证:

1,pathAuthorizer

根据请求的URL Path,是否在excludedPaths中,或者前缀一致的带*的Path,如果在就直接返回权限允许。

func NewAuthorizer(alwaysAllowPaths []string) (authorizer.Authorizer, error) {
   var prefixes []string
   paths := sets.NewString()
   for _, p := range alwaysAllowPaths {
      p = strings.TrimPrefix(p, "/")
      if len(p) == 0 {
         // matches "/"
         paths.Insert(p)
         continue
      }
      if strings.ContainsRune(p[:len(p)-1], '*') {
         return nil, fmt.Errorf("only trailing * allowed in %q", p)
      }
      if strings.HasSuffix(p, "*") {
         prefixes = append(prefixes, p[:len(p)-1])
      } else {
         paths.Insert(p)
      }
   }

   return authorizer.AuthorizerFunc(func(a authorizer.Attributes) (authorizer.Decision, string, error) {
      pth := strings.TrimPrefix(a.GetPath(), "/")
      if paths.Has(pth) {
         return authorizer.DecisionAllow, "", nil
      }

      for _, prefix := range prefixes {
         if strings.HasPrefix(pth, prefix) {
            return authorizer.DecisionAllow, "", nil
         }
      }

      return authorizer.DecisionNoOpinion, "", nil
   }), nil
}

2,RBACAuthorizer

RBACAuthorizer调用visitRulesFor对请求的属性进行权限验证

func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
   ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes}

   r.visitRulesFor(requestAttributes, ruleCheckingVisitor.visit)

   if ruleCheckingVisitor.allowed {
      return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil
   }

}

visitRulesFor根据请求的资源范围属性,查找对应的rolebinding,将查找到的rolebinding绑定的rule规则信息和请求的信息作为参数给visitor函数进行规则比对,如果比对匹配成功,返回权限允许。

可以看到代码中:

1,查找ListGlobalRoleBindings全局role绑定

2,与每条globalRoleBinding进行比对,如果请求的用户,用户所属的group,用户对应的ServiceAccount与globalRoleBinding的subjects中的User, Group, ServiceAccount相同,则进行第3步

3,查找globalRoleBinding对应的Role,这里有两种规则:regoPolicy, rules,regoPolicy是写在key为"iam.kubesphere.io/rego-override"Annotations,rules为真正的role的权限规则。

4,调用visitor来进行权限规则匹配,这里会先比对regoPolicy,再比对rules,一个满足就可以了。在rule比对中,根据Verb, APIGroup, Resource, ResourceName来进行判断,完全匹配就allow。

func (r *RBACAuthorizer) visitRulesFor(requestAttributes authorizer.Attributes, visitor func(source fmt.Stringer, regoPolicy string, rule *rbacv1.PolicyRule, err error) bool) {

   if globalRoleBindings, err := r.am.ListGlobalRoleBindings(""); err != nil {
      if !visitor(nil, "", nil, err) {
         return
      }
   } else {
      sourceDescriber := &globalRoleBindingDescriber{}
      for _, globalRoleBinding := range globalRoleBindings {
         subjectIndex, applies := appliesTo(requestAttributes.GetUser(), globalRoleBinding.Subjects, "")
         if !applies {
            continue
         }
         regoPolicy, rules, err := r.am.GetRoleReferenceRules(globalRoleBinding.RoleRef, "")
         if err != nil {
            visitor(nil, "", nil, err)
            continue
         }
         sourceDescriber.binding = globalRoleBinding
         sourceDescriber.subject = &globalRoleBinding.Subjects[subjectIndex]
         if !visitor(sourceDescriber, regoPolicy, nil, nil) {
            return
         }
         for i := range rules {
            if !visitor(sourceDescriber, "", &rules[i], nil) {
               return
            }
         }
      }

      if requestAttributes.GetResourceScope() == request.GlobalScope {
         return
      }
   }

   if requestAttributes.GetResourceScope() == request.WorkspaceScope ||
      requestAttributes.GetResourceScope() == request.NamespaceScope ||
      requestAttributes.GetResourceScope() == request.DevOpsScope {

      var workspace string
      var err error
      // all of resource under namespace and devops belong to workspace
      if requestAttributes.GetResourceScope() == request.NamespaceScope {
         if workspace, err = r.am.GetNamespaceControlledWorkspace(requestAttributes.GetNamespace()); err != nil {
            if !visitor(nil, "", nil, err) {
               return
            }
         }
      } else if requestAttributes.GetResourceScope() == request.DevOpsScope {
         if workspace, err = r.am.GetDevOpsControlledWorkspace(requestAttributes.GetDevOps()); err != nil {
            if !visitor(nil, "", nil, err) {
               return
            }
         }
      }

      if workspace == "" {
         workspace = requestAttributes.GetWorkspace()
      }

      if workspaceRoleBindings, err := r.am.ListWorkspaceRoleBindings("", nil, workspace); err != nil {
         if !visitor(nil, "", nil, err) {
            return
         }
      } else {
         sourceDescriber := &workspaceRoleBindingDescriber{}
         for _, workspaceRoleBinding := range workspaceRoleBindings {
            subjectIndex, applies := appliesTo(requestAttributes.GetUser(), workspaceRoleBinding.Subjects, "")
            if !applies {
               continue
            }
            regoPolicy, rules, err := r.am.GetRoleReferenceRules(workspaceRoleBinding.RoleRef, "")
            if err != nil {
               visitor(nil, "", nil, err)
               continue
            }
            sourceDescriber.binding = workspaceRoleBinding
            sourceDescriber.subject = &workspaceRoleBinding.Subjects[subjectIndex]
            if !visitor(sourceDescriber, regoPolicy, nil, nil) {
               return
            }
            for i := range rules {
               if !visitor(sourceDescriber, "", &rules[i], nil) {
                  return
               }
            }
         }
      }
   }

   if requestAttributes.GetResourceScope() == request.NamespaceScope ||
      requestAttributes.GetResourceScope() == request.DevOpsScope {

      namespace := requestAttributes.GetNamespace()
      // list devops role binding
      if requestAttributes.GetResourceScope() == request.DevOpsScope {
         if relatedNamespace, err := r.am.GetDevOpsRelatedNamespace(requestAttributes.GetDevOps()); err != nil {
            if !visitor(nil, "", nil, err) {
               return
            }
         } else {
            namespace = relatedNamespace
         }
      }

      if roleBindings, err := r.am.ListRoleBindings("", nil, namespace); err != nil {
         if !visitor(nil, "", nil, err) {
            return
         }
      } else {
         sourceDescriber := &roleBindingDescriber{}
         for _, roleBinding := range roleBindings {
            subjectIndex, applies := appliesTo(requestAttributes.GetUser(), roleBinding.Subjects, namespace)
            if !applies {
               continue
            }
            regoPolicy, rules, err := r.am.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
            if err != nil {
               visitor(nil, "", nil, err)
               continue
            }
            sourceDescriber.binding = roleBinding
            sourceDescriber.subject = &roleBinding.Subjects[subjectIndex]
            if !visitor(sourceDescriber, regoPolicy, nil, nil) {
               return
            }
            for i := range rules {
               if !visitor(sourceDescriber, "", &rules[i], nil) {
                  return
               }
            }
         }
      }
   }

   if clusterRoleBindings, err := r.am.ListClusterRoleBindings(""); err != nil {
      if !visitor(nil, "", nil, err) {
         return
      }
   } else {
      sourceDescriber := &clusterRoleBindingDescriber{}
      for _, clusterRoleBinding := range clusterRoleBindings {
         subjectIndex, applies := appliesTo(requestAttributes.GetUser(), clusterRoleBinding.Subjects, "")
         if !applies {
            continue
         }
         regoPolicy, rules, err := r.am.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
         if err != nil {
            visitor(nil, "", nil, err)
            continue
         }
         sourceDescriber.binding = clusterRoleBinding
         sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex]
         if !visitor(sourceDescriber, regoPolicy, nil, nil) {
            return
         }
         for i := range rules {
            if !visitor(sourceDescriber, "", &rules[i], nil) {
               return
            }
         }
      }
   }
}

func (v *authorizingVisitor) visit(source fmt.Stringer, regoPolicy string, rule *rbacv1.PolicyRule, err error) bool {
	if regoPolicy != "" && regoPolicyAllows(v.requestAttributes, regoPolicy) {
		v.allowed = true
		v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String())
		return false
	}
	if rule != nil && ruleAllows(v.requestAttributes, rule) {
		v.allowed = true
		v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String())
		return false
	}
	if err != nil {
		v.errors = append(v.errors, err)
	}
	return true
}


func ruleAllows(requestAttributes authorizer.Attributes, rule *rbacv1.PolicyRule) bool {
	if requestAttributes.IsResourceRequest() {
		combinedResource := requestAttributes.GetResource()
		if len(requestAttributes.GetSubresource()) > 0 {
			combinedResource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource()
		}

		return VerbMatches(rule, requestAttributes.GetVerb()) &&
			APIGroupMatches(rule, requestAttributes.GetAPIGroup()) &&
			ResourceMatches(rule, combinedResource, requestAttributes.GetSubresource()) &&
			ResourceNameMatches(rule, requestAttributes.GetName())
	}

	return VerbMatches(rule, requestAttributes.GetVerb()) &&
		NonResourceURLMatches(rule, requestAttributes.GetPath())
}

func regoPolicyAllows(requestAttributes authorizer.Attributes, regoPolicy string) bool {
	// Call the rego.New function to create an object that can be prepared or evaluated
	//  After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
	query, err := rego.New(rego.Query(defaultRegoQuery), rego.Module(defaultRegoFileName, regoPolicy)).PrepareForEval(context.Background())

	if err != nil {
		klog.Warningf("syntax error:%s, content: %s", err, regoPolicy)
		return false
	}

	// The policy decision is contained in the results returned by the Eval() call. You can inspect the decision and handle it accordingly.
	results, err := query.Eval(context.Background(), rego.EvalInput(requestAttributes))

	if err != nil {
		klog.Warningf("syntax error:%s, content: %s", err, regoPolicy)
		return false
	}

	if len(results) > 0 && results[0].Expressions[0].Value == true {
		return true
	}

	return false
}

至此,可以看到处理过程通过Context保存用户认证后用户信息以及API请求信息,最终通过规则匹配进行比对,整个鉴权过程就完成了,权限通过的可以进行API请求处理,拒绝的返回403。