k8s源码分析7-API核心服务Authorization的认证及鉴权

一、Authentication认证

本节重点总结 :

  • Authentication的目的

  • Kubernetes 使用身份认证插件利用下面的策略来认证 API 请求的身份

    • 客户端证书
    • 持有者令牌(Bearer Token)
    • 身份认证代理(Proxy)
    • HTTP 基本认证机制
  • union认证的规则

    • 如果某一个认证方法报错就返回,说明认证没过
    • 如果某一个认证方法报ok,说明认证过了,直接return了,无需再运行其他认证了
    • 如果所有的认证方法都没报ok,则认证没过

Authentication的目的

k8s_authentication.png

  • 验证你是谁 确认“你是不是你",包括多种方式,如 Client Certificates, Password, and Plain Tokens, Bootstrap Tokens, and JWT Tokens等
  • 文档 地址 https://kubernetes.io/zh/docs/reference/access-authn-authz/authentication/
  • 所有 Kubernetes 集群都有两类用户:由 Kubernetes 管理的服务账号和普通用户
  • 所以认证要围绕这两类用户展开

身份认证策略

  • Kubernetes 使用身份认证插件利用客户端证书、持有者令牌(Bearer Token)、身份认证代理(Proxy) 或者 HTTP 基本认证机制来认证 API 请求的身份

  • HTTP 请求发给 API 服务器时, 插件会将以下属性关联到请求本身:

    • 用户名:用来辩识最终用户的字符串。常见的值可以是 kube-admin 或 jane@example.com。
    • 用户 ID:用来辩识最终用户的字符串,旨在比用户名有更好的一致性和唯一性。
    • 用户组:取值为一组字符串,其中各个字符串用来标明用户是某个命名的用户逻辑集合的成员。 常见的值可能是 system:masters 或者 devops-team 等。
    • 附加字段:一组额外的键-值映射,键是字符串,值是一组字符串;用来保存一些鉴权组件可能 觉得有用的额外信息。
  • 你可以同时启用多种身份认证方法,并且你通常会至少使用两种方法:

    • 针对服务账号使用服务账号令牌
    • 至少另外一种方法对用户的身份进行认证
  • 当集群中启用了多个身份认证模块时,第一个成功地对请求完成身份认证的模块会 直接做出评估决定。API 服务器并不保证身份认证模块的运行顺序

  • 对于所有通过身份认证的用户,system:authenticated 组都会被添加到其组列表中。

  • 与其它身份认证协议(LDAP、SAML、Kerberos、X509 的替代模式等等)都可以通过 使用一个身份认证代理或 身份认证 Webhoook来实现。

代码解读

  • D:\go_path\src\github.com\kubernetes\kubernetes\cmd\kube-apiserver\app\server.go
  • 之前构建server之前生成通用配置buildGenericConfig里
	// Authentication.ApplyTo requires already applied OpenAPIConfig and EgressSelector if present
	if lastErr = s.Authentication.ApplyTo(&genericConfig.Authentication, genericConfig.SecureServing, genericConfig.EgressSelector, genericConfig.OpenAPIConfig, clientgoExternalClient, versionedInformers); lastErr != nil {
		return
	}

真正的 Authentication初始化

  • D:\go_path\src\github.com\kubernetes\kubernetes\pkg\kubeapiserver\options\authentication.go
authInfo.Authenticator, openAPIConfig.SecurityDefinitions, err = authenticatorConfig.New()

New代码 、创建认证实例,支持多种认证方式:请求 Header 认证、Auth 文件认证、CA 证书认证、Bearer token 认证、

  • D:\go_path\src\github.com\kubernetes\kubernetes\pkg\kubeapiserver\authenticator\config.go

核心变量1 tokenAuthenticators []authenticator.Token 代表Bearer token 认证

// Token checks a string value against a backing authentication store and
// returns a Response or an error if the token could not be checked.
type Token interface {
	AuthenticateToken(ctx context.Context, token string) (*Response, bool, error)
}

  • 不断添加到数组中,最终创建union对象,最终调用unionAuthTokenHandler.AuthenticateToken
tokenAuth := tokenunion.New(tokenAuthenticators...)
func (authHandler *unionAuthTokenHandler) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
	var errlist []error
	for _, currAuthRequestHandler := range authHandler.Handlers {
		info, ok, err := currAuthRequestHandler.AuthenticateToken(ctx, token)
		if err != nil {
			if authHandler.FailOnError {
				return info, ok, err
			}
			errlist = append(errlist, err)
			continue
		}

		if ok {
			return info, ok, err
		}
	}

	return nil, false, utilerrors.NewAggregate(errlist)
}

核心变量 2 authenticator.Request代表 用户认证的接口 ,其中AuthenticateRequest是对应的认证方法

// Request attempts to extract authentication information from a request and
// returns a Response or an error if the request could not be checked.
type Request interface {
	AuthenticateRequest(req *http.Request) (*Response, bool, error)
}

  • 然后不断添加到切片中,比如x509认证
	// X509 methods
	if config.ClientCAContentProvider != nil {
		certAuth := x509.NewDynamic(config.ClientCAContentProvider.VerifyOptions, x509.CommonNameUserConversion)
		authenticators = append(authenticators, certAuth)
	}
  • 把上面的unionAuthTokenHandler 也加入到链中
authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
  • 最后创建一个union对象 unionAuthRequestHandler
authenticator := union.New(authenticators...)
  • 最终调用的unionAuthRequestHandler.AuthenticateRequest方法遍历认证方法认证
// AuthenticateRequest authenticates the request using a chain of authenticator.Request objects.
func (authHandler *unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
	var errlist []error
	for _, currAuthRequestHandler := range authHandler.Handlers {
		resp, ok, err := currAuthRequestHandler.AuthenticateRequest(req)
		if err != nil {
			if authHandler.FailOnError {
				return resp, ok, err
			}
			errlist = append(errlist, err)
			continue
		}

		if ok {
			return resp, ok, err
		}
	}

	return nil, false, utilerrors.NewAggregate(errlist)
}

  • 代码解读:
    • 如果某一个认证方法报错就返回,说明认证没过
    • 如果某一个认证方法报ok,说明认证过了,直接return了,无需再运行其他认证了
    • 如果所有的认证方法都没报ok,则认证没过

本节重点总结 :

  • Authentication的目的

  • Kubernetes 使用身份认证插件利用下面的策略来认证 API 请求的身份

    • 客户端证书
    • 持有者令牌(Bearer Token)
    • 身份认证代理(Proxy)
    • HTTP 基本认证机制
  • union认证的规则

    • 如果某一个认证方法报错就返回,说明认证没过
    • 如果某一个认证方法报ok,说明认证过了,直接return了,无需再运行其他认证了
    • 如果所有的认证方法都没报ok,则认证没过

二、Authorization 鉴权

本节重点总结 :

  • Authorization 鉴权的目的
  • 4种鉴权模块
  • 鉴权执行链unionAuthzHandler

Authorization 鉴权相关

image

  • Authorization鉴权,确认“你是不是有权利做这件事”。怎样判定是否有权利,通过配置策略
  • Kubernetes 使用 API 服务器对 API 请求进行鉴权
  • 它根据所有策略评估所有请求属性来决定允许或拒绝请求
  • 一个 API 请求的所有部分都必须被某些策略允许才能继续。 这意味着默认情况下拒绝权限。
  • 当系统配置了多个鉴权模块时,Kubernetes 将按顺序使用每个模块。 如果任何鉴权模块批准或拒绝请求,则立即返回该决定,并且不会与其他鉴权模块协商。 如果所有模块对请求没有意见,则拒绝该请求。 被拒绝响应返回 HTTP 状态代码 403。
  • 文档地址 https://kubernetes.io/zh/docs/reference/access-authn-authz/authorization/

4种鉴权模块

  • 文档地址 https://kubernetes.io/zh/docs/reference/access-authn-authz/authorization/#authorization-modules
  • Node - 一个专用鉴权组件,根据调度到 kubelet 上运行的 Pod 为 kubelet 授予权限。 了解有关使用节点鉴权模式的更多信息,请参阅节点鉴权。
  • ABAC - 基于属性的访问控制(ABAC)定义了一种访问控制范型,通过使用将属性组合 在一起的策略,将访问权限授予用户。策略可以使用任何类型的属性(用户属性、资源属性、 对象,环境属性等)。要了解有关使用 ABAC 模式的更多信息,请参阅 ABAC 模式。
  • RBAC - 基于角色的访问控制(RBAC)是一种基于企业内个人用户的角色来管理对 计算机或网络资源的访问的方法。在此上下文中,权限是单个用户执行特定任务的能力, 例如查看、创建或修改文件。要了解有关使用 RBAC 模式的更多信息,请参阅 RBAC 模式。
    • 被启用之后,RBAC(基于角色的访问控制)使用 rbac.authorization.k8s.io API 组来 驱动鉴权决策,从而允许管理员通过 Kubernetes API 动态配置权限策略。
    • 要启用 RBAC,请使用 --authorization-mode = RBAC 启动 API 服务器。
  • Webhook - WebHook 是一个 HTTP 回调:发生某些事情时调用的 HTTP POST; 通过 HTTP POST 进行简单的事件通知。实现 WebHook 的 Web 应用程序会在发生某些事情时 将消息发布到 URL。要了解有关使用 Webhook 模式的更多信息,请参阅 Webhook 模式。

代码解析

  • 入口还在buildGenericConfig D:\go_path\src\github.com\kubernetes\kubernetes\cmd\kube-apiserver\app\server.go
genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers)

  • 还是通过New构造 ,位置 D:\go_path\src\github.com\kubernetes\kubernetes\pkg\kubeapiserver\authorizer\config.go
authorizationConfig.New()

构造函数New分析

核心变量1 authorizers

  • D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\pkg\authorization\authorizer\interfaces.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(ctx context.Context, a Attributes) (authorized Decision, reason string, err error)
}
  • 鉴权的接口,有对应的Authorize执行鉴权操作,返回参数如下

  • Decision代表鉴权结果,有

    • 拒绝 DecisionDeny
    • 通过 DecisionAllow
    • 未表态 DecisionNoOpinion
  • reason代表拒绝的原因

核心变量2 ruleResolvers

  • D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\pkg\authorization\authorizer\interfaces.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)
}
  • 获取rule的接口,有对应的RulesFor执行获取rule操作,返回参数如下
  • []ResourceRuleInfo代表资源型的rule
  • []NonResourceRuleInfo代表非资源型的如 nonResourceURLs: ["/metrics"]

遍历鉴权模块判断,向上述切片中append

	for _, authorizationMode := range config.AuthorizationModes {
		// Keep cases in sync with constant list in k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes/modes.go.
		switch authorizationMode {
		case modes.ModeNode:
			node.RegisterMetrics()
			graph := node.NewGraph()
			node.AddGraphEventHandlers(
				graph,
				config.VersionedInformerFactory.Core().V1().Nodes(),
				config.VersionedInformerFactory.Core().V1().Pods(),
				config.VersionedInformerFactory.Core().V1().PersistentVolumes(),
				config.VersionedInformerFactory.Storage().V1().VolumeAttachments(),
			)
			nodeAuthorizer := node.NewAuthorizer(graph, nodeidentifier.NewDefaultNodeIdentifier(), bootstrappolicy.NodeRules())
			authorizers = append(authorizers, nodeAuthorizer)
			ruleResolvers = append(ruleResolvers, nodeAuthorizer)
		case modes.ModeRBAC:
			rbacAuthorizer := rbac.New(
				&rbac.RoleGetter{Lister: config.VersionedInformerFactory.Rbac().V1().Roles().Lister()},
				&rbac.RoleBindingLister{Lister: config.VersionedInformerFactory.Rbac().V1().RoleBindings().Lister()},
				&rbac.ClusterRoleGetter{Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoles().Lister()},
				&rbac.ClusterRoleBindingLister{Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoleBindings().Lister()},
			)
			authorizers = append(authorizers, rbacAuthorizer)
			ruleResolvers = append(ruleResolvers, rbacAuthorizer)
    }

  • 最后返回两个对象的union对象,跟authentication一样
	return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil

authorizers的union unionAuthzHandler

  • 位置 D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\pkg\authorization\union\union.go
// New returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer {
	return unionAuthzHandler(authorizationHandlers)
}

// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful
func (authzHandler unionAuthzHandler) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
	var (
		errlist    []error
		reasonlist []string
	)

	for _, currAuthzHandler := range authzHandler {
		decision, reason, err := currAuthzHandler.Authorize(ctx, 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)
}

  • unionAuthzHandler的鉴权执行方法 Authorize同样是遍历执行内部的鉴权方法Authorize
  • 如果任一方法的鉴权结果decision为通过或者拒绝,就直接返回
  • 否则代表不表态,继续执行下一个Authorize方法

ruleResolvers的union unionAuthzHandler

  • 位置 D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\pkg\authorization\union\union.go
// unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver
type unionAuthzRulesHandler []authorizer.RuleResolver

// NewRuleResolvers returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
func NewRuleResolvers(authorizationHandlers ...authorizer.RuleResolver) authorizer.RuleResolver {
	return unionAuthzRulesHandler(authorizationHandlers)
}

// RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful
func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
	var (
		errList              []error
		resourceRulesList    []authorizer.ResourceRuleInfo
		nonResourceRulesList []authorizer.NonResourceRuleInfo
	)
	incompleteStatus := false

	for _, currAuthzHandler := range authzHandler {
		resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace)

		if incomplete {
			incompleteStatus = true
		}
		if err != nil {
			errList = append(errList, err)
		}
		if len(resourceRules) > 0 {
			resourceRulesList = append(resourceRulesList, resourceRules...)
		}
		if len(nonResourceRules) > 0 {
			nonResourceRulesList = append(nonResourceRulesList, nonResourceRules...)
		}
	}

	return resourceRulesList, nonResourceRulesList, incompleteStatus, utilerrors.NewAggregate(errList)
}

  • unionAuthzRulesHandler的执行方法RulesFor中遍历内部的authzHandler
  • 执行他们的RulesFor方法获取resourceRules和nonResourceRules
  • 并将结果添加到resourceRulesList和nonResourceRulesList,返回

本节重点总结 :

  • Authorization 鉴权的目的
  • 4种鉴权模块
  • 鉴权执行链unionAuthzHandler
posted @ 2022-12-04 11:41  西门运维  阅读(440)  评论(0编辑  收藏  举报