You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kubeSourceCodeNote/apiServer/Kubernetes源码学习-APIServer-P4...

656 lines
25 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## 前言
在上一篇[**APIServer-P3-APIServer的认证机制**](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/apiServer/Kubernetes%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0-APIServer-P3-APIServer%E7%9A%84%E8%AE%A4%E8%AF%81%E6%9C%BA%E5%88%B6.md)中,讲述了请求进入后的认证过程,在通过认证之后,请求将进入鉴权环节,本篇就此展开。
## 审查请求属性
Kubernetes 仅审查以下 API 请求属性:
- **用户** - 身份验证期间提供的 `user` 字符串。
- **组** - 经过身份验证的用户所属的组名列表。
- **额外信息** - 由身份验证层提供的任意字符串键到字符串值的映射。
- **API** - 指示请求是否针对 API 资源。
- **请求路径** - 各种非资源端点的路径,如 `/api``/healthz`
- **API 请求动词** - API 动词 `get`、`list`、`create`、`update`、`patch`、`watch`、 `proxy`、`redirect`、`delete` 和 `deletecollection` 用于资源请求。 要确定资源 API 端点的请求动词,请参阅 [确定请求动词](https://kubernetes.io/zh/docs/reference/access-authn-authz/authorization/#determine-the-request-verb)。
- **HTTP 请求动词** - HTTP 动词 `get`、`post`、`put` 和 `delete` 用于非资源请求。
- **Resource** - 正在访问的资源的 ID 或名称(仅限资源请求)- 对于使用 `get`、`update`、`patch` 和 `delete` 动词的资源请求,你必须提供资源名称。
- **子资源** - 正在访问的子资源(仅限资源请求)。
- **名字空间** - 正在访问的对象的名称空间(仅适用于名字空间资源请求)。
- **API 组** - 正在访问的 [API 组](https://kubernetes.io/zh/docs/concepts/overview/kubernetes-api/#api-groups) (仅限资源请求)。空字符串表示[核心 API 组](https://kubernetes.io/zh/docs/reference/using-api/#api-groups)
## 鉴权的描述
### 鉴权策略分类
目前支持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部署集群启用的鉴权策略:
![](https://mycloudn.upweto.top/20210203155452.png)
可以看到默认启用了Node授权和RBAC授权模块。
### 鉴权结果
对于每一个请求的鉴权结果,有专门为其设计的健全结果描述结构体,如下:
`vendor/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go:148`
```go
type Decision int
const (
// 拒绝
DecisionDeny Decision = iota
// 允许,则鉴权流程视为成功,请求顺利进入
DecisionAllow
// 无操作进入下一个鉴权模块相当于pass
DecisionNoOpinion
)
```
### 鉴权接口方法
`vendor/k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go:69`
```go
// Authorizer makes an authorization decision based on information gained by making
// zero or more calls to methods of the Attributes interface. It returns nil when an action is
// authorized, otherwise it returns an error.
type Authorizer interface {
Authorize(a Attributes) (authorized Decision, reason string, err error)
}
```
所有的鉴权模块(鉴权器)都要实现这个`Authorize`方法,返回鉴权结果。
### 规则解析器
规则解析器可以根据认证之后所得到的用户信息,获取该用户对应的资源对象的操作权限。
```go
// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
type RuleResolver interface {
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
}
```
以这里的返回值类型`ResourceRuleInfo`为例,默认的`DefaultResourceRuleInfo`结构体是这样的:
`vendor/k8s.io/apiserver/pkg/authorization/authorizer/rule.go:31`
```go
// DefaultResourceRuleInfo holds information that describes a rule for the resource
type DefaultResourceRuleInfo struct {
Verbs []string
APIGroups []string
Resources []string
ResourceNames []string
}
```
例如对pod资源的任意操作权限的描述可以描述为:
```go
DefaultResourceRuleInfo{
Verbs []string{"*"}
APIGroups []string{"*"}
Resources []string{"pod"}
}
```
这个DefaultResourceRuleInfo对象描述的规则是允许对所有api group 的pod资源进行的所有类型的操作包括{"get", "list", "update", "patch","create", "delete", "watch", "deletecollection"}操作。
### 鉴权流程图
![](https://mycloudn.upweto.top/20210203180748.png)
## 鉴权器
`AlwaysAllow`和`AlwaysDeny`这两种鉴权器很少使用,就不看了,直接略过.
### ABAC鉴权器
#### 简介
基于属性的访问控制Attribute-based access control - ABAC定义了访问控制范例其中通过使用将属性组合在一起的策略来向用户授予访问权限。
启用ABAC鉴权器需要额外增加一个`--authorization-policy-file=SOME_FILENAME`参数指定一个json格式的文件预设鉴权策略是一种静态的权限配置方式。json格式样例如下
```json
// 授予pod资源的任意操作权限给用户podManager
{
"apiVersion":"abac.authorization.kubernetes.io/v1beta1",
"kind":"Policy",
"spec":{
"user":"podManager",
"namespace":"*",
"resource":"pods",
"readonly":true
}
}
```
#### 代码实现
`pkg/auth/authorizer/abac/abac.go:224`
```go
// Authorizer implements authorizer.Authorize
func (pl policyList) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
for _, p := range pl {
if matches(*p, a) {
return authorizer.DecisionAllow, "", nil
}
}
return authorizer.DecisionNoOpinion, "No policy matched.", nil
}
```
--> `pkg/auth/authorizer/abac/abac.go:117`
```go
func matches(p abac.Policy, a authorizer.Attributes) bool {
if subjectMatches(p, a.GetUser()) {
// 操作类型与规则匹配
if verbMatches(p, a) {
// Resource and non-resource requests are mutually exclusive, at most one will match a policy
// 资源类型与规则匹配(包含namespace/APIGroup/Resource)
if resourceMatches(p, a) {
return true
}
// 针对非资源对象的操作匹配(请求路径匹配)
if nonResourceMatches(p, a) {
return true
}
}
}
return false
}
```
### RBAC鉴权器
#### 简介
基于角色Role的访问控制RBAC是一种基于组织中用户的角色来调节控制对 计算机或网络资源的访问的方法。
RBAC 鉴权机制使用 `rbac.authorization.k8s.io` [API 组](https://kubernetes.io/zh/docs/concepts/overview/kubernetes-api/#api-groups) 来驱动鉴权决定,允许你通过 Kubernetes API 动态配置策略。
通过创建`Role` 或` ClusterRole`来描述具体的资源授权策略再通过创建RoleBinding/ClusterRoleBinding将策略绑定到用户/群组/服务上。
RBAC模式的详细描述和使用样例请参考我之前的文章
[k8s(十四)、RBAC权限控制](https://blog.upweto.top/2018/12/06/k8s(%E5%8D%81%E5%9B%9B)%E3%80%81RBAC%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6.html)
#### 代码实现
`plugin/pkg/auth/authorizer/rbac/rbac.go:74`
```go
func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes}
// 规则解析器解析请求的属性返回鉴权结果判断匹配用的是ruleCheckingVisitor.visit方法
r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit)
if ruleCheckingVisitor.allowed {
return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil
}
...
reason := ""
if len(ruleCheckingVisitor.errors) > 0 {
reason = fmt.Sprintf("RBAC: %v", utilerrors.NewAggregate(ruleCheckingVisitor.errors))
}
return authorizer.DecisionNoOpinion, reason, nil
}
```
对比规则和请求属性返回true or false的visit方法
`plugin/pkg/auth/authorizer/rbac/rbac.go:62`
```go
func (v *authorizingVisitor) visit(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool {
if rule != nil && RuleAllows(v.requestAttributes, rule) {
// 请求与规则匹配,则鉴权成功
v.allowed = true
v.reason = fmt.Sprintf("RBAC: allowed by %s", source.String())
// 返回false是为了提前break鉴权过程
return false
}
if err != nil {
v.errors = append(v.errors, err)
}
return true
}
```
获取规则并调用visit方法的是`VisitRulesFor`接口方法,找一下`VisitRulesFor`方法:
`pkg/registry/rbac/validation/rule.go:178`
```go
func (r *DefaultRuleResolver) VisitRulesFor(user user.Info, namespace string, visitor func(source fmt.Stringer, rule *rbacv1.PolicyRule, err error) bool) {
// 先拿到所有的ClusterRoleBinding对象ClusterRoleBinding资源是cluster级别的不区分命名空间
if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
if !visitor(nil, nil, err) {
return
}
} else {
sourceDescriber := &clusterRoleBindingDescriber{}
for _, clusterRoleBinding := range clusterRoleBindings {
// clusterRoleBinding.Subjects指定的绑定的用户对象对比请求的所属用户不匹配则continue
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 {
// visit方法返回false是代表鉴权成功了提前break鉴权过程
if !visitor(sourceDescriber, &rules[i], nil) {
return
}
}
}
}
// 如果指定了namespace再取命名空间级别的roleBinding资源对象重复一次上面的过程
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
}
}
}
}
}
}
```
#### 过程总结
参考代码片中的注释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`
```go
func NodeRules() []rbacv1.PolicyRule {
nodePolicyRules := []rbacv1.PolicyRule{
// Needed to check API access. These creates are non-mutating
rbacv1helpers.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
rbacv1helpers.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(),
// Needed to build serviceLister, to populate env vars for services
rbacv1helpers.NewRule(Read...).Groups(legacyGroup).Resources("services").RuleOrDie(),
// Nodes can register Node API objects and report status.
// Use the NodeRestriction admission plugin to limit a node to creating/updating its own API object.
rbacv1helpers.NewRule("create", "get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
// TODO: restrict to the bound node as creator in the NodeRestrictions admission plugin
rbacv1helpers.NewRule("create", "update", "patch").Groups(legacyGroup).Resources("events").RuleOrDie(),
// TODO: restrict to pods scheduled on the bound node once field selectors are supported by list/watch authorization
rbacv1helpers.NewRule(Read...).Groups(legacyGroup).Resources("pods").RuleOrDie(),
// Needed for the node to create/delete mirror pods.
// Use the NodeRestriction admission plugin to limit a node to creating/deleting mirror pods bound to itself.
rbacv1helpers.NewRule("create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// Needed for the node to report status of pods it is running.
// Use the NodeRestriction admission plugin to limit a node to updating status of pods bound to itself.
rbacv1helpers.NewRule("update", "patch").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
// Needed for the node to create pod evictions.
// Use the NodeRestriction admission plugin to limit a node to creating evictions for pods bound to itself.
rbacv1helpers.NewRule("create").Groups(legacyGroup).Resources("pods/eviction").RuleOrDie(),
// Needed for imagepullsecrets, rbd/ceph and secret volumes, and secrets in envs
// Needed for configmap volume and envs
// Use the Node authorization mode to limit a node to get secrets/configmaps referenced by pods bound to itself.
rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("secrets", "configmaps").RuleOrDie(),
// Needed for persistent volumes
// Use the Node authorization mode to limit a node to get pv/pvc objects referenced by pods bound to itself.
rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(),
// TODO: add to the Node authorizer and restrict to endpoints referenced by pods or PVs bound to the node
// Needed for glusterfs volumes
rbacv1helpers.NewRule("get").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
// Used to create a certificatesigningrequest for a node-specific client certificate, and watch
// for it to be signed. This allows the kubelet to rotate it's own certificate.
rbacv1helpers.NewRule("create", "get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(),
}
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
// Use the Node authorization mode to limit a node to update status of pvc objects referenced by pods bound to itself.
// Use the NodeRestriction admission plugin to limit a node to just update the status stanza.
pvcStatusPolicyRule := rbacv1helpers.NewRule("get", "update", "patch").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie()
nodePolicyRules = append(nodePolicyRules, pvcStatusPolicyRule)
}
if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) {
// Use the Node authorization to limit a node to create tokens for service accounts running on that node
// Use the NodeRestriction admission plugin to limit a node to create tokens bound to pods on that node
tokenRequestRule := rbacv1helpers.NewRule("create").Groups(legacyGroup).Resources("serviceaccounts/token").RuleOrDie()
nodePolicyRules = append(nodePolicyRules, tokenRequestRule)
}
// CSI
if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) {
volAttachRule := rbacv1helpers.NewRule("get").Groups(storageGroup).Resources("volumeattachments").RuleOrDie()
nodePolicyRules = append(nodePolicyRules, volAttachRule)
if utilfeature.DefaultFeatureGate.Enabled(features.CSIDriverRegistry) {
csiDriverRule := rbacv1helpers.NewRule("get", "watch", "list").Groups("storage.k8s.io").Resources("csidrivers").RuleOrDie()
nodePolicyRules = append(nodePolicyRules, csiDriverRule)
}
}
if utilfeature.DefaultFeatureGate.Enabled(features.KubeletPluginsWatcher) &&
utilfeature.DefaultFeatureGate.Enabled(features.CSINodeInfo) {
csiNodeInfoRule := rbacv1helpers.NewRule("get", "create", "update", "patch", "delete").Groups("storage.k8s.io").Resources("csinodes").RuleOrDie()
nodePolicyRules = append(nodePolicyRules, csiNodeInfoRule)
}
// Node leases
if utilfeature.DefaultFeatureGate.Enabled(features.NodeLease) {
nodePolicyRules = append(nodePolicyRules, rbacv1helpers.NewRule("get", "create", "update", "patch", "delete").Groups("coordination.k8s.io").Resources("leases").RuleOrDie())
}
// RuntimeClass
if utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClass) {
nodePolicyRules = append(nodePolicyRules, rbacv1helpers.NewRule("get", "list", "watch").Groups("node.k8s.io").Resources("runtimeclasses").RuleOrDie())
}
return nodePolicyRules
}
```
这里初始化了kubelet工作所需要的资源的权限如(node/pod/cm/secret/pvc等)
#### Authorize代码实现
`plugin/pkg/auth/authorizer/node/node_authorizer.go:80`
```go
func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (authorizer.Decision, string, error) {
// 判断是不是node发起的请求(所属的group是不是system:node)
nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser())
if !isNode {
// reject requests from non-nodes
return authorizer.DecisionNoOpinion, "", nil
}
if len(nodeName) == 0 {
// reject requests from unidentifiable nodes
klog.V(2).Infof("NODE DENY: unknown node for user %q", attrs.GetUser().GetName())
return authorizer.DecisionNoOpinion, fmt.Sprintf("unknown node for user %q", attrs.GetUser().GetName()), nil
}
// subdivide access to specific resources
if attrs.IsResourceRequest() {
// 根据请求属性(路径)获取资源类型,不同类型资源不同的方式处理
requestResource := schema.GroupResource{Group: attrs.GetAPIGroup(), Resource: attrs.GetResource()}
switch requestResource {
case secretResource:
return r.authorizeReadNamespacedObject(nodeName, secretVertexType, attrs)
case configMapResource:
return r.authorizeReadNamespacedObject(nodeName, configMapVertexType, attrs)
case pvcResource:
if r.features.Enabled(features.ExpandPersistentVolumes) {
if attrs.GetSubresource() == "status" {
return r.authorizeStatusUpdate(nodeName, pvcVertexType, attrs)
}
}
return r.authorizeGet(nodeName, pvcVertexType, attrs)
case pvResource:
return r.authorizeGet(nodeName, pvVertexType, attrs)
case vaResource:
if r.features.Enabled(features.CSIPersistentVolume) {
return r.authorizeGet(nodeName, vaVertexType, attrs)
}
return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.CSIPersistentVolume), nil
case svcAcctResource:
if r.features.Enabled(features.TokenRequest) {
return r.authorizeCreateToken(nodeName, serviceAccountVertexType, attrs)
}
return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.TokenRequest), nil
case leaseResource:
if r.features.Enabled(features.NodeLease) {
return r.authorizeLease(nodeName, attrs)
}
return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gate %s", features.NodeLease), nil
case csiNodeResource:
if r.features.Enabled(features.KubeletPluginsWatcher) && r.features.Enabled(features.CSINodeInfo) {
return r.authorizeCSINode(nodeName, attrs)
}
return authorizer.DecisionNoOpinion, fmt.Sprintf("disabled by feature gates %s and %s", features.KubeletPluginsWatcher, features.CSINodeInfo), nil
}
}
// Access to other resources is not subdivided, so just evaluate against the statically defined node rules
if rbac.RulesAllow(attrs, r.nodeRules...) {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionNoOpinion, "", nil
}
```
### WebHook鉴权器
#### 简介
与上一篇中的WebHook认证器类似WebHook鉴权器也是依赖于集群外部的鉴权服务器将鉴权请求POST发送给外部的鉴权服务器。
`Webhook` 模式需要一个 HTTP 配置文件,通过 `--authorization-webhook-config-file=SOME_FILENAME` 的参数声明。
配置文件的格式使用 [kubeconfig](https://kubernetes.io/zh/docs/tasks/access-application-cluster/configure-access-multiple-clusters/)。在文件中,"users" 代表着 API 服务器的 webhook而 "cluster" 代表着远程服务。
使用 HTTPS 客户端认证的配置例子:
```yaml
# Kubernetes API 版本
apiVersion: v1
# API 对象种类
kind: Config
# clusters 代表远程服务。
clusters:
- name: name-of-remote-authz-service
cluster:
# 对远程服务进行身份认证的 CA。
certificate-authority: /path/to/ca.pem
# 远程服务的查询 URL。必须使用 'https'。
server: https://authz.example.com/authorize
# users 代表 API 服务器的 webhook 配置
users:
- name: name-of-api-server
user:
client-certificate: /path/to/cert.pem # webhook plugin 使用 cert
client-key: /path/to/key.pem # cert 所对应的 key
# kubeconfig 文件必须有 context。需要提供一个给 API 服务器。
current-context: webhook
contexts:
- context:
cluster: name-of-remote-authz-service
user: name-of-api-server
name: webhook
```
> 摘自官方文档[Webhook 模式](https://kubernetes.io/zh/docs/reference/access-authn-authz/webhook/)
#### 代码实现
`vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go:152`
```go
func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
r := &authorization.SubjectAccessReview{}
if user := attr.GetUser(); user != nil {
r.Spec = authorization.SubjectAccessReviewSpec{
User: user.GetName(),
UID: user.GetUID(),
Groups: user.GetGroups(),
Extra: convertToSARExtra(user.GetExtra()),
}
}
if attr.IsResourceRequest() {
r.Spec.ResourceAttributes = &authorization.ResourceAttributes{
Namespace: attr.GetNamespace(),
Verb: attr.GetVerb(),
Group: attr.GetAPIGroup(),
Version: attr.GetAPIVersion(),
Resource: attr.GetResource(),
Subresource: attr.GetSubresource(),
Name: attr.GetName(),
}
} else {
r.Spec.NonResourceAttributes = &authorization.NonResourceAttributes{
Path: attr.GetPath(),
Verb: attr.GetVerb(),
}
}
// 将请求的主体/资源/操作等字段放在一个json里
key, err := json.Marshal(r.Spec)
if err != nil {
return w.decisionOnError, "", err
}
// 从本地的缓存里取有则不发起远端post请求了
if entry, ok := w.responseCache.Get(string(key)); ok {
r.Status = entry.(authorization.SubjectAccessReviewStatus)
} else {
var (
result *authorization.SubjectAccessReview
err error
)
webhook.WithExponentialBackoff(w.initialBackoff, func() error {
// 缓存里没有则发起post请求给远端鉴权服务器
result, err = w.subjectAccessReview.Create(r)
return err
})
if err != nil {
// An error here indicates bad configuration or an outage. Log for debugging.
klog.Errorf("Failed to make webhook authorizer request: %v", err)
return w.decisionOnError, "", err
}
r.Status = result.Status
// 长度不超过10000则缓存结果
if shouldCache(attr) {
if r.Status.Allowed {
w.responseCache.Add(string(key), r.Status, w.authorizedTTL)
} else {
w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL)
}
}
}
switch {
// 根据远端鉴权服务器的响应状态,返回鉴权结果
case r.Status.Denied && r.Status.Allowed:
return authorizer.DecisionDeny, r.Status.Reason, fmt.Errorf("webhook subject access review returned both allow and deny response")
case r.Status.Denied:
return authorizer.DecisionDeny, r.Status.Reason, nil
case r.Status.Allowed:
return authorizer.DecisionAllow, r.Status.Reason, nil
default:
return authorizer.DecisionNoOpinion, r.Status.Reason, nil
}
}
```
## 总结
鉴权的流程与认证的流程大体类似,但也有所不同,例如认证器链的执行顺序是无序的,而鉴权器链的执行顺序是有序的(按参数指定的顺序)。另外鉴权器的数量没有认证器那么多,因此相对容易理解一些。