Kubernetes源码学习-APIServer-P4-APIServer的鉴权机制

た 入场券 2022-10-27 12:29 292阅读 0赞

前言

在上一篇APIServer-P3-APIServer的认证机制中,讲述了请求进入后的认证过程,在通过认证之后,请求将进入鉴权环节,本篇就此展开。

审查请求属性

Kubernetes 仅审查以下 API 请求属性:

  • 用户 - 身份验证期间提供的 user 字符串。
  • - 经过身份验证的用户所属的组名列表。
  • 额外信息 - 由身份验证层提供的任意字符串键到字符串值的映射。
  • API - 指示请求是否针对 API 资源。
  • 请求路径 - 各种非资源端点的路径,如 /api/healthz
  • API 请求动词 - API 动词 getlistcreateupdatepatchwatchproxyredirectdeletedeletecollection 用于资源请求。 要确定资源 API 端点的请求动词,请参阅 确定请求动词。
  • HTTP 请求动词 - HTTP 动词 getpostputdelete 用于非资源请求。
  • Resource - 正在访问的资源的 ID 或名称(仅限资源请求)- 对于使用 getupdatepatchdelete 动词的资源请求,你必须提供资源名称。
  • 子资源 - 正在访问的子资源(仅限资源请求)。
  • 名字空间 - 正在访问的对象的名称空间(仅适用于名字空间资源请求)。
  • API 组 - 正在访问的 API 组 (仅限资源请求)。空字符串表示核心 API 组

鉴权的描述

鉴权策略分类

目前支持6种鉴权策略,每种鉴权策略对应一个鉴权器,使用的鉴权策略需要在APIServer启动时以参数--authorization-mode的形式指定,多种策略同时指定时使用’,’号连接:

策略分类有:

  • --authorization-mode=ABAC 基于属性的访问控制(ABAC)模式允许你 使用本地文件配置策略。
  • --authorization-mode=RBAC 基于角色的访问控制(RBAC)模式允许你使用 Kubernetes API 创建和存储策略。
  • --authorization-mode=Webhook WebHook 是一种 HTTP 回调模式,允许你使用远程 REST 端点管理鉴权。
  • --authorization-mode=Node 节点鉴权是一种特殊用途的鉴权模式,专门对 kubelet 发出的 API 请求执行鉴权。
  • --authorization-mode=AlwaysDeny 该标志阻止所有请求。仅将此标志用于测试。
  • --authorization-mode=AlwaysAllow 此标志允许所有请求。仅在你不需要 API 请求 的鉴权时才使用此标志。

与上一篇的认证模块不同的是,当配置多个鉴权模块时,鉴权模块按顺序检查,靠前的模块具有更高的优先级来允许或拒绝请求。

来看看现有的kubeadm部署集群启用的鉴权策略:

f4ca97ec0aa4f71ca80f4937d25dfbc5.png

可以看到,默认启用了Node授权和RBAC授权模块。

鉴权结果

对于每一个请求的鉴权结果,有专门为其设计的健全结果描述结构体,如下:

vendor/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go:148

  1. type Decision int
  2. const (
  3. // 拒绝
  4. DecisionDeny Decision = iota
  5. // 允许,则鉴权流程视为成功,请求顺利进入
  6. DecisionAllow
  7. // 无操作,进入下一个鉴权模块,相当于pass
  8. DecisionNoOpinion
  9. )

鉴权接口方法

vendor/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go:69

  1. // Authorizer makes an authorization decision based on information gained by making
  2. // zero or more calls to methods of the Attributes interface. It returns nil when an action is
  3. // authorized, otherwise it returns an error.
  4. type Authorizer interface {
  5. Authorize(a Attributes) (authorized Decision, reason string, err error)
  6. }

所有的鉴权模块(鉴权器)都要实现这个Authorize方法,返回鉴权结果。

规则解析器

规则解析器可以根据认证之后所得到的用户信息,获取该用户对应的资源对象的操作权限。

  1. // RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
  2. type RuleResolver interface {
  3. // RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
  4. RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
  5. }

以这里的返回值类型ResourceRuleInfo为例,默认的DefaultResourceRuleInfo结构体是这样的:

vendor/k8s.io/apiserver/pkg/authorization/authorizer/rule.go:31

  1. // DefaultResourceRuleInfo holds information that describes a rule for the resource
  2. type DefaultResourceRuleInfo struct {
  3. Verbs []string
  4. APIGroups []string
  5. Resources []string
  6. ResourceNames []string
  7. }

例如对pod资源的任意操作权限的描述可以描述为:

  1. DefaultResourceRuleInfo{
  2. Verbs []string{ "*"}
  3. APIGroups []string{ "*"}
  4. Resources []string{ "pod"}
  5. }

这个DefaultResourceRuleInfo对象描述的规则是,允许对所有api group 的pod资源进行的所有类型的操作,包括{“get”, “list”, “update”, “patch”,“create”, “delete”, “watch”, “deletecollection”}操作。

鉴权流程图

f30cfa9babebc9700d626df79028f2e6.png

鉴权器

AlwaysAllowAlwaysDeny这两种鉴权器很少使用,就不看了,直接略过.

ABAC鉴权器

简介

基于属性的访问控制(Attribute-based access control - ABAC)定义了访问控制范例,其中通过使用将属性组合在一起的策略来向用户授予访问权限。

启用ABAC鉴权器需要额外增加一个--authorization-policy-file=SOME_FILENAME参数,指定一个json格式的文件预设鉴权策略,是一种静态的权限配置方式。json格式样例如下:

  1. // 授予pod资源的任意操作权限给用户podManager
  2. {
  3. "apiVersion":"abac.authorization.kubernetes.io/v1beta1",
  4. "kind":"Policy",
  5. "spec":{
  6. "user":"podManager",
  7. "namespace":"*",
  8. "resource":"pods",
  9. "readonly":true
  10. }
  11. }

代码实现

pkg/auth/authorizer/abac/abac.go:224

  1. // Authorizer implements authorizer.Authorize
  2. func (pl policyList) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
  3. for _, p := range pl {
  4. if matches(*p, a) {
  5. return authorizer.DecisionAllow, "", nil
  6. }
  7. }
  8. return authorizer.DecisionNoOpinion, "No policy matched.", nil
  9. }

–> pkg/auth/authorizer/abac/abac.go:117

  1. func matches(p abac.Policy, a authorizer.Attributes) bool {
  2. if subjectMatches(p, a.GetUser()) {
  3. // 操作类型与规则匹配
  4. if verbMatches(p, a) {
  5. // Resource and non-resource requests are mutually exclusive, at most one will match a policy
  6. // 资源类型与规则匹配(包含namespace/APIGroup/Resource)
  7. if resourceMatches(p, a) {
  8. return true
  9. }
  10. // 针对非资源对象的操作匹配(请求路径匹配)
  11. if nonResourceMatches(p, a) {
  12. return true
  13. }
  14. }
  15. }
  16. return false
  17. }

RBAC鉴权器

简介

基于角色(Role)的访问控制(RBAC)是一种基于组织中用户的角色来调节控制对 计算机或网络资源的访问的方法。

RBAC 鉴权机制使用 rbac.authorization.k8s.io API 组 来驱动鉴权决定,允许你通过 Kubernetes API 动态配置策略。

通过创建RoleClusterRole来描述具体的资源授权策略,再通过创建RoleBinding/ClusterRoleBinding将策略绑定到用户/群组/服务上。

RBAC模式的详细描述和使用样例请参考我之前的文章:

k8s(十四)、RBAC权限控制

代码实现

plugin/pkg/auth/authorizer/rbac/rbac.go:74

  1. func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
  2. ruleCheckingVisitor := &authorizingVisitor{ requestAttributes: requestAttributes}
  3. // 规则解析器解析请求的属性,返回鉴权结果,判断匹配用的是ruleCheckingVisitor.visit方法
  4. r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit)
  5. if ruleCheckingVisitor.allowed {
  6. return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil
  7. }
  8. ...
  9. reason := ""
  10. if len(ruleCheckingVisitor.errors) > 0 {
  11. reason = fmt.Sprintf("RBAC: %v", utilerrors.NewAggregate(ruleCheckingVisitor.errors))
  12. }
  13. return authorizer.DecisionNoOpinion, reason, nil
  14. }

对比规则和请求属性,返回true or false的visit方法:

plugin/pkg/auth/authorizer/rbac/rbac.go:62

  1. func (v *authorizingVisitor) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool {
  2. if rule != nil && RuleAllows(v.requestAttributes, rule) {
  3. // 请求与规则匹配,则鉴权成功
  4. v.allowed = true
  5. v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String())
  6. // 返回false是为了提前break鉴权过程
  7. return false
  8. }
  9. if err != nil {
  10. v.errors = append(v.errors, err)
  11. }
  12. return true
  13. }

获取规则并调用visit方法的是VisitRulesFor接口方法,找一下VisitRulesFor方法:

pkg/registry/rbac/validation/rule.go:178

  1. func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
  2. // 先拿到所有的ClusterRoleBinding对象,ClusterRoleBinding资源是cluster级别的,不区分命名空间
  3. if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
  4. if !visitor(nil, nil, err) {
  5. return
  6. }
  7. } else {
  8. sourceDescriber := &clusterRoleBindingDescriber{ }
  9. for _, clusterRoleBinding := range clusterRoleBindings {
  10. // clusterRoleBinding.Subjects指定的绑定的用户对象,对比请求的所属用户,不匹配则continue
  11. subjectIndex, applies := appliesTo(user, clusterRoleBinding.Subjects, "")
  12. if !applies {
  13. continue
  14. }
  15. rules, err := r.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
  16. if err != nil {
  17. if !visitor(nil, nil, err) {
  18. return
  19. }
  20. continue
  21. }
  22. sourceDescriber.binding = clusterRoleBinding
  23. sourceDescriber.subject = &clusterRoleBinding.Subjects[subjectIndex]
  24. for i := range rules {
  25. // visit方法返回false是代表鉴权成功了,提前break鉴权过程
  26. if !visitor(sourceDescriber, &rules[i], nil) {
  27. return
  28. }
  29. }
  30. }
  31. }
  32. // 如果指定了namespace,再取命名空间级别的roleBinding资源对象,重复一次上面的过程
  33. if len(namespace) > 0 {
  34. if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
  35. if !visitor(nil, nil, err) {
  36. return
  37. }
  38. } else {
  39. sourceDescriber := &roleBindingDescriber{ }
  40. for _, roleBinding := range roleBindings {
  41. subjectIndex, applies := appliesTo(user, roleBinding.Subjects, namespace)
  42. if !applies {
  43. continue
  44. }
  45. rules, err := r.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
  46. if err != nil {
  47. if !visitor(nil, nil, err) {
  48. return
  49. }
  50. continue
  51. }
  52. sourceDescriber.binding = roleBinding
  53. sourceDescriber.subject = &roleBinding.Subjects[subjectIndex]
  54. for i := range rules {
  55. if !visitor(sourceDescriber, &rules[i], nil) {
  56. return
  57. }
  58. }
  59. }
  60. }
  61. }
  62. }

过程总结

参考代码片中的注释,rbac鉴权过程如下:

  • 1.取到所有的clusterRoleBinding/roleBindings资源对象,遍历它们对比请求用户
  • 2.对比roleBindings/clusterRoleBinding指向的用户(主体)与请求用户,相同则选中,不相同continue
  • 3.对比规则与请求属性,符合则提前结束鉴权

Node鉴权器

上面有提过,node鉴权器是专为kubelet组件设计的,按照kubeadm集群的默认配置,它是排序在第一位的鉴权器,为什么把它放在后面再讲呢,因为node鉴权器本质上也是利用了rbac鉴权器,是通过为system:node这个内置用户授权来实现的,来看一下。

默认Node规则生成

plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go:97

  1. func NodeRules() []rbacv1.PolicyRule {
  2. nodePolicyRules := []rbacv1.PolicyRule{
  3. // Needed to check API access. These creates are non-mutating
  4. rbacv1helpers.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
  5. rbacv1helpers.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(),
  6. // Needed to build serviceLister, to populate env vars for services
  7. rbacv1helpers.NewRule(Read...).Groups(legacyGroup).Resources("services").RuleOrDie(),
  8. // Nodes can register Node API objects and report status.
  9. // Use the NodeRestriction admission plugin to limit a node to creating/updating its own API object.
  10. rbacv1helpers.NewRule("create", "get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
  11. rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
  12. rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
  13. // TODO: restrict to the bound node as creator in the NodeRestrictions admission plugin
  14. rbacv1helpers.NewRule("create", "update", "patch").Groups(legacyGroup).Resources("events").RuleOrDie(),
  15. // TODO: restrict to pods scheduled on the bound node once field selectors are supported by list/watch authorization
  16. rbacv1helpers.NewRule(Read...).Groups(legacyGroup).Resources("pods").RuleOrDie(),
  17. // Needed for the node to create/delete mirror pods.
  18. // Use the NodeRestriction admission plugin to limit a node to creating/deleting mirror pods bound to itself.
  19. rbacv1helpers.NewRule("create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
  20. // Needed for the node to report status of pods it is running.
  21. // Use the NodeRestriction admission plugin to limit a node to updating status of pods bound to itself.
  22. rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
  23. // Needed for the node to create pod evictions.
  24. // Use the NodeRestriction admission plugin to limit a node to creating evictions for pods bound to itself.
  25. rbacv1helpers.NewRule("create").Groups(legacyGroup).Resources("pods/eviction").RuleOrDie(),
  26. // Needed for imagepullsecrets, rbd/ceph and secret volumes, and secrets in envs
  27. // Needed for configmap volume and envs
  28. // Use the Node authorization mode to limit a node to get secrets/configmaps referenced by pods bound to itself.
  29. rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("secrets", "configmaps").RuleOrDie(),
  30. // Needed for persistent volumes
  31. // Use the Node authorization mode to limit a node to get pv/pvc objects referenced by pods bound to itself.
  32. rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(),
  33. // TODO: add to the Node authorizer and restrict to endpoints referenced by pods or PVs bound to the node
  34. // Needed for glusterfs volumes
  35. rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
  36. // Used to create a certificatesigningrequest for a node-specific client certificate, and watch
  37. // for it to be signed. This allows the kubelet to rotate it's own certificate.
  38. rbacv1helpers.NewRule("create", "get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(),
  39. }
  40. if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
  41. // Use the Node authorization mode to limit a node to update status of pvc objects referenced by pods bound to itself.
  42. // Use the NodeRestriction admission plugin to limit a node to just update the status stanza.
  43. pvcStatusPolicyRule := rbacv1helpers.NewRule("get", "update", "patch").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie()
  44. nodePolicyRules = append(nodePolicyRules, pvcStatusPolicyRule)
  45. }
  46. if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) {
  47. // Use the Node authorization to limit a node to create tokens for service accounts running on that node
  48. // Use the NodeRestriction admission plugin to limit a node to create tokens bound to pods on that node
  49. tokenRequestRule := rbacv1helpers.NewRule("create").Groups(legacyGroup).Resources("serviceaccounts/token").RuleOrDie()
  50. nodePolicyRules = append(nodePolicyRules, tokenRequestRule)
  51. }
  52. // CSI
  53. if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) {
  54. volAttachRule := rbacv1helpers.NewRule("get").Groups(storageGroup).Resources("volumeattachments").RuleOrDie()
  55. nodePolicyRules = append(nodePolicyRules, volAttachRule)
  56. if utilfeature.DefaultFeatureGate.Enabled(features.CSIDriverRegistry) {
  57. csiDriverRule := rbacv1helpers.NewRule("get", "watch", "list").Groups("storage.k8s.io").Resources("csidrivers").RuleOrDie()
  58. nodePolicyRules = append(nodePolicyRules, csiDriverRule)
  59. }
  60. }
  61. if utilfeature.DefaultFeatureGate.Enabled(features.KubeletPluginsWatcher) &&
  62. utilfeature.DefaultFeatureGate.Enabled(features.CSINodeInfo) {
  63. csiNodeInfoRule := rbacv1helpers.NewRule("get", "create", "update", "patch", "delete").Groups("storage.k8s.io").Resources("csinodes").RuleOrDie()
  64. nodePolicyRules = append(nodePolicyRules, csiNodeInfoRule)
  65. }
  66. // Node leases
  67. if utilfeature.DefaultFeatureGate.Enabled(features.NodeLease) {
  68. nodePolicyRules = append(nodePolicyRules, rbacv1helpers.NewRule("get", "create", "update", "patch", "delete").Groups("coordination.k8s.io").Resources("leases").RuleOrDie())
  69. }
  70. // RuntimeClass
  71. if utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClass) {
  72. nodePolicyRules = append(nodePolicyRules, rbacv1helpers.NewRule("get", "list", "watch").Groups("node.k8s.io").Resources("runtimeclasses").RuleOrDie())
  73. }
  74. return nodePolicyRules
  75. }

这里初始化了kubelet工作所需要的资源的权限,如(node/pod/cm/secret/pvc等)

Authorize代码实现

plugin/pkg/auth/authorizer/node/node_authorizer.go:80

  1. func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Decision, string, error) {
  2. // 判断是不是node发起的请求(所属的group是不是system:node)
  3. nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser())
  4. if !isNode {
  5. // reject requests from non-nodes
  6. return authorizer.DecisionNoOpinion, "", nil
  7. }
  8. if len(nodeName) == 0 {
  9. // reject requests from unidentifiable nodes
  10. klog.V(2).Infof("NODE DENY: unknown node for user %q", attrs.GetUser().GetName())
  11. return authorizer.DecisionNoOpinion, fmt.Sprintf("unknown node for user %q", attrs.GetUser().GetName()), nil
  12. }
  13. // subdivide access to specific resources
  14. if attrs.IsResourceRequest() {
  15. // 根据请求属性(路径)获取资源类型,不同类型资源不同的方式处理
  16. requestResource := schema.GroupResource{ Group: attrs.GetAPIGroup(), Resource: attrs.GetResource()}
  17. switch requestResource {
  18. case secretResource:
  19. return r.authorizeReadNamespacedObject(nodeName, secretVertexType, attrs)
  20. case configMapResource:
  21. return r.authorizeReadNamespacedObject(nodeName, configMapVertexType, attrs)
  22. case pvcResource:
  23. if r.features.Enabled(features.ExpandPersistentVolumes) {
  24. if attrs.GetSubresource() == "status" {
  25. return r.authorizeStatusUpdate(nodeName, pvcVertexType, attrs)
  26. }
  27. }
  28. return r.authorizeGet(nodeName, pvcVertexType, attrs)
  29. case pvResource:
  30. return r.authorizeGet(nodeName, pvVertexType, attrs)
  31. case vaResource:
  32. if r.features.Enabled(features.CSIPersistentVolume) {
  33. return r.authorizeGet(nodeName, vaVertexType, attrs)
  34. }
  35. return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.CSIPersistentVolume), nil
  36. case svcAcctResource:
  37. if r.features.Enabled(features.TokenRequest) {
  38. return r.authorizeCreateToken(nodeName, serviceAccountVertexType, attrs)
  39. }
  40. return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.TokenRequest), nil
  41. case leaseResource:
  42. if r.features.Enabled(features.NodeLease) {
  43. return r.authorizeLease(nodeName, attrs)
  44. }
  45. return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.NodeLease), nil
  46. case csiNodeResource:
  47. if r.features.Enabled(features.KubeletPluginsWatcher) && r.features.Enabled(features.CSINodeInfo) {
  48. return r.authorizeCSINode(nodeName, attrs)
  49. }
  50. return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gates %s and %s", features.KubeletPluginsWatcher, features.CSINodeInfo), nil
  51. }
  52. }
  53. // Access to other resources is not subdivided, so just evaluate against the statically defined node rules
  54. if rbac.RulesAllow(attrs, r.nodeRules...) {
  55. return authorizer.DecisionAllow, "", nil
  56. }
  57. return authorizer.DecisionNoOpinion, "", nil
  58. }

WebHook鉴权器

简介

与上一篇中的WebHook认证器类似,WebHook鉴权器也是依赖于集群外部的鉴权服务器,将鉴权请求POST发送给外部的鉴权服务器。

Webhook 模式需要一个 HTTP 配置文件,通过 --authorization-webhook-config-file=SOME_FILENAME 的参数声明。

配置文件的格式使用 kubeconfig。在文件中,“users” 代表着 API 服务器的 webhook,而 “cluster” 代表着远程服务。

使用 HTTPS 客户端认证的配置例子:

  1. # Kubernetes API 版本
  2. apiVersion: v1
  3. # API 对象种类
  4. kind: Config
  5. # clusters 代表远程服务。
  6. clusters:
  7. - name: name-of-remote-authz-service
  8. cluster:
  9. # 对远程服务进行身份认证的 CA。
  10. certificate-authority: /path/to/ca.pem
  11. # 远程服务的查询 URL。必须使用 'https'。
  12. server: https://authz.example.com/authorize
  13. # users 代表 API 服务器的 webhook 配置
  14. users:
  15. - name: name-of-api-server
  16. user:
  17. client-certificate: /path/to/cert.pem # webhook plugin 使用 cert
  18. client-key: /path/to/key.pem # cert 所对应的 key
  19. # kubeconfig 文件必须有 context。需要提供一个给 API 服务器。
  20. current-context: webhook
  21. contexts:
  22. - context:
  23. cluster: name-of-remote-authz-service
  24. user: name-of-api-server
  25. name: webhook

摘自官方文档Webhook 模式

代码实现

vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go:152

  1. func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
  2. r := &authorization.SubjectAccessReview{ }
  3. if user := attr.GetUser(); user != nil {
  4. r.Spec = authorization.SubjectAccessReviewSpec{
  5. User: user.GetName(),
  6. UID: user.GetUID(),
  7. Groups: user.GetGroups(),
  8. Extra: convertToSARExtra(user.GetExtra()),
  9. }
  10. }
  11. if attr.IsResourceRequest() {
  12. r.Spec.ResourceAttributes = &authorization.ResourceAttributes{
  13. Namespace: attr.GetNamespace(),
  14. Verb: attr.GetVerb(),
  15. Group: attr.GetAPIGroup(),
  16. Version: attr.GetAPIVersion(),
  17. Resource: attr.GetResource(),
  18. Subresource: attr.GetSubresource(),
  19. Name: attr.GetName(),
  20. }
  21. } else {
  22. r.Spec.NonResourceAttributes = &authorization.NonResourceAttributes{
  23. Path: attr.GetPath(),
  24. Verb: attr.GetVerb(),
  25. }
  26. }
  27. // 将请求的主体/资源/操作等字段放在一个json里
  28. key, err := json.Marshal(r.Spec)
  29. if err != nil {
  30. return w.decisionOnError, "", err
  31. }
  32. // 从本地的缓存里取,有则不发起远端post请求了
  33. if entry, ok := w.responseCache.Get(string(key)); ok {
  34. r.Status = entry.(authorization.SubjectAccessReviewStatus)
  35. } else {
  36. var (
  37. result *authorization.SubjectAccessReview
  38. err error
  39. )
  40. webhook.WithExponentialBackoff(w.initialBackoff, func() error {
  41. // 缓存里没有,则发起post请求给远端鉴权服务器
  42. result, err = w.subjectAccessReview.Create(r)
  43. return err
  44. })
  45. if err != nil {
  46. // An error here indicates bad configuration or an outage. Log for debugging.
  47. klog.Errorf("Failed to make webhook authorizer request: %v", err)
  48. return w.decisionOnError, "", err
  49. }
  50. r.Status = result.Status
  51. // 长度不超过10000则缓存结果
  52. if shouldCache(attr) {
  53. if r.Status.Allowed {
  54. w.responseCache.Add(string(key), r.Status, w.authorizedTTL)
  55. } else {
  56. w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL)
  57. }
  58. }
  59. }
  60. switch {
  61. // 根据远端鉴权服务器的响应状态,返回鉴权结果
  62. case r.Status.Denied && r.Status.Allowed:
  63. return authorizer.DecisionDeny, r.Status.Reason, fmt.Errorf("webhook subject access review returned both allow and deny response")
  64. case r.Status.Denied:
  65. return authorizer.DecisionDeny, r.Status.Reason, nil
  66. case r.Status.Allowed:
  67. return authorizer.DecisionAllow, r.Status.Reason, nil
  68. default:
  69. return authorizer.DecisionNoOpinion, r.Status.Reason, nil
  70. }
  71. }

总结

鉴权的流程与认证的流程大体类似,但也有所不同,例如认证器链的执行顺序是无序的,而鉴权器链的执行顺序是有序的(按参数指定的顺序)。另外鉴权器的数量没有认证器那么多,因此相对容易理解一些。

发表评论

表情:
评论列表 (有 0 条评论,292人围观)

还没有评论,来说两句吧...

相关阅读

    相关 5.4 交易

    5.4.1 账户权限相关概念 权限 EOS采用父子分层的权限结构,低级权限(子权限)由高级权限(父权限)派生而来,父权限拥有子权限所有的能力。子权限能做的事父权限也能做