k8s源码分析7-API核心服务Authorization的认证及鉴权
一、Authentication认证
本节重点总结 :
-
Authentication的目的
-
Kubernetes 使用身份认证插件利用下面的策略来认证 API 请求的身份
- 客户端证书
- 持有者令牌(Bearer Token)
- 身份认证代理(Proxy)
- HTTP 基本认证机制
-
union认证的规则
- 如果某一个认证方法报错就返回,说明认证没过
- 如果某一个认证方法报ok,说明认证过了,直接return了,无需再运行其他认证了
- 如果所有的认证方法都没报ok,则认证没过
Authentication的目的
- 验证你是谁 确认“你是不是你",包括多种方式,如 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 鉴权相关
- 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
如对您有帮助,支持下呗!
微信
支付宝