Kubernetes:kube-apiserver 之鉴权
kubernetes:kube-apiserver 系列文章:
- Kubernetes:kube-apiserver 之 scheme(一)
 - Kubernetes:kube-apiserver 之 scheme(二)
 - Kubernetes:kube-apiserver 之啟動(dòng)流程(一)
 - Kubernetes:kube-apiserver 之啟動(dòng)流程(二)
 - Kubernetes:kube-apiserver 和 etcd 的交互
 - Kubernetes:kube-apiserver 之認(rèn)證
 
0. 前言
上一篇文章介紹了 kube-apiserver 的認(rèn)證機(jī)制。這里繼續(xù)往下走,介紹 kube-apiserver 的鑒權(quán)。kube-apiserver 處理認(rèn)證和鑒權(quán)非常類似,建議閱讀鑒權(quán)機(jī)制前先看看 kube-apiserver 的 認(rèn)證。
1. 鑒權(quán) Authorization
1.1 Authorization handler
進(jìn)入 DefaultBuildHandlerChain 看 Authorization handler 創(chuàng)建過(guò)程。
# kubernetes/vendor/k8s.io/apiserver/pkg/server/config.go
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
	handler := apiHandler
	handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
	handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authorization")
    handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences, c.Authentication.RequestHeaderConfig)
	handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authentication")
}
這里 handler chain 的 handler 處理順序是由下往上的,即處理完 authentication handler 在處理 authorization handler。
進(jìn)入 genericapifilters.WithAuthorization 查看鑒權(quán) handler 的創(chuàng)建過(guò)程。
# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
func WithAuthorization(hhandler http.Handler, auth authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
	return withAuthorization(hhandler, auth, s, recordAuthorizationMetrics)
}
func withAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer, metrics recordAuthorizationMetricsFunc) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		ctx := req.Context()
		authorizationStart := time.Now()
        // 獲取 request 攜帶的用戶信息
		attributes, err := GetAuthorizerAttributes(ctx)
		if err != nil {
			responsewriters.InternalError(w, req, err)
			return
		}
        // 對(duì)用戶信息進(jìn)行鑒權(quán)
		authorized, reason, err := a.Authorize(ctx, attributes)
		...
	})
}
鑒權(quán)過(guò)程包括兩部分。
一部分通過(guò) GetAuthorizerAttributes 獲取 RESTful API 請(qǐng)求中攜帶的用戶信息。
# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
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.ResourceRequest = requestInfo.IsResourceRequest
	attribs.Path = requestInfo.Path
	attribs.Verb = requestInfo.Verb
	attribs.APIGroup = requestInfo.APIGroup
	attribs.APIVersion = requestInfo.APIVersion
	attribs.Resource = requestInfo.Resource
	attribs.Subresource = requestInfo.Subresource
	attribs.Namespace = requestInfo.Namespace
	attribs.Name = requestInfo.Name
	return &attribs, nil
}
獲取到用戶信息后通過(guò) a.Authorize(ctx, attributes) 對(duì)用戶及其請(qǐng)求進(jìn)行鑒權(quán)。其中 a 是實(shí)現(xiàn)了 Authorizer 鑒權(quán)器接口的實(shí)例。
type Authorizer interface {
	Authorize(ctx context.Context, a Attributes) (authorized Decision, reason string, err error)
}
查看 a.Authorize(ctx, attributes) 實(shí)際是看 Config.Authorization.Authorizer 中的實(shí)例。
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
	handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
}
1.2 Authorization authorizer
Config.Authorization.Authorizer 在 BuildGenericConfig 的 BuildAuthorizer 函數(shù)內(nèi)創(chuàng)建。
# kubernetes/pkg/controlplane/apiserver/config.go
func BuildGenericConfig(
	s controlplaneapiserver.CompletedOptions,
	schemes []*runtime.Scheme,
	getOpenAPIDefinitions func(ref openapicommon.ReferenceCallback) map[string]openapicommon.OpenAPIDefinition,
) (
	genericConfig *genericapiserver.Config,
	versionedInformers clientgoinformers.SharedInformerFactory,
	storageFactory *serverstorage.DefaultStorageFactory,
	lastErr error,
) {
    genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers)
	if err != nil {
		lastErr = fmt.Errorf("invalid authorization config: %v", err)
		return
	}
}
進(jìn)入 BuildAuthorizer 查看 Authorizer 是怎么創(chuàng)建的。
# kubernetes/pkg/controlplane/apiserver/config.go
func BuildAuthorizer(s controlplaneapiserver.CompletedOptions, EgressSelector *egressselector.EgressSelector, versionedInformers clientgoinformers.SharedInformerFactory) (authorizer.Authorizer, authorizer.RuleResolver, error) {
	authorizationConfig := s.Authorization.ToAuthorizationConfig(versionedInformers)
	if EgressSelector != nil {
		egressDialer, err := EgressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext())
		if err != nil {
			return nil, nil, err
		}
		authorizationConfig.CustomDial = egressDialer
	}
	return authorizationConfig.New()
}
創(chuàng)建 Authorizer 分為兩塊。首先創(chuàng)建 authorizationConfig,接著通過(guò) authorizationConfig.New() 實(shí)例化 authorizer。
# kubernetes/pkg/kubeapiserver/authorizer/config.go
func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
	var (
		authorizers   []authorizer.Authorizer
		ruleResolvers []authorizer.RuleResolver
	)
	for _, authorizationMode := range config.AuthorizationModes {
		switch authorizationMode {
		case modes.ModeNode:
			...
		case modes.ModeAlwaysAllow:
			...
		case modes.ModeAlwaysDeny:
            ...
		case modes.ModeABAC:
			...
		case modes.ModeWebhook:
			...
		case modes.ModeRBAC:
			...
		default:
			return nil, nil, fmt.Errorf("unknown authorization mode %s specified", authorizationMode)
		}
	}
	return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil
}
可以看到,authorizationConfig.New() 內(nèi)根據(jù) config.AuthorizationModes 確定需要?jiǎng)?chuàng)建的鑒權(quán)器類型。這里有 modes.ModeNode,modes.ModeAlwaysAllow,modes.ModeAlwaysDeny,modes.ModeABAC,modes.ModeWebhook 和 modes.ModeRBAC 六種鑒權(quán)器。
config.AuthorizationModes 是在創(chuàng)建選項(xiàng) NewOptions 中定義的,實(shí)例化過(guò)程如下:
func (o *BuiltInAuthorizationOptions) ToAuthorizationConfig(versionedInformerFactory versionedinformers.SharedInformerFactory) authorizer.Config {
	return authorizer.Config{
		AuthorizationModes:          o.Modes,
	}
}
# kubernetes/pkg/controlplane/apiserver/options/options.go
func NewOptions() *Options {
	s := Options{
		Authorization:           kubeoptions.NewBuiltInAuthorizationOptions()
	}
	return &s
}
# kubernetes/pkg/kubeapiserver/options/authorization.go
func NewBuiltInAuthorizationOptions() *BuiltInAuthorizationOptions {
	return &BuiltInAuthorizationOptions{
		Modes:                       []string{authzmodes.ModeAlwaysAllow},
		WebhookVersion:              "v1beta1",
		WebhookCacheAuthorizedTTL:   5 * time.Minute,
		WebhookCacheUnauthorizedTTL: 30 * time.Second,
		WebhookRetryBackoff:         genericoptions.DefaultAuthWebhookRetryBackoff(),
	}
}
這里的 config.AuthorizationModes 為 authzmodes.ModeAlwaysAllow。那么,將創(chuàng)建 alwaysAllowAuthorizer 鑒權(quán)器。
# kubernetes/pkg/kubeapiserver/authorizer/config.go
func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
	var (
		authorizers   []authorizer.Authorizer
		ruleResolvers []authorizer.RuleResolver
	)
	for _, authorizationMode := range config.AuthorizationModes {
		switch authorizationMode {
		case modes.ModeAlwaysAllow:
			alwaysAllowAuthorizer := authorizerfactory.NewAlwaysAllowAuthorizer()
			authorizers = append(authorizers, alwaysAllowAuthorizer)
			ruleResolvers = append(ruleResolvers, alwaysAllowAuthorizer)
        }
    }
    return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil
}
# kubernetes/vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go
func NewAlwaysAllowAuthorizer() *alwaysAllowAuthorizer {
	return new(alwaysAllowAuthorizer)
}
type alwaysAllowAuthorizer struct{}
func (alwaysAllowAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
	return authorizer.DecisionAllow, "", nil
}
func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
	return []authorizer.ResourceRuleInfo{
			&authorizer.DefaultResourceRuleInfo{
				Verbs:     []string{"*"},
				APIGroups: []string{"*"},
				Resources: []string{"*"},
			},
		}, []authorizer.NonResourceRuleInfo{
			&authorizer.DefaultNonResourceRuleInfo{
				Verbs:           []string{"*"},
				NonResourceURLs: []string{"*"},
			},
		}, false, nil
}
alwaysAllowAuthorizer 鑒權(quán)器實(shí)現(xiàn)了 Authorizer 接口,其總是返回 authorizer.DecisionAllow。
每種鑒權(quán)器通過(guò) union.New 加到鑒權(quán)器組中。
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)
}
前面 handler 的 a.Authorize(ctx, attributes) 實(shí)際執(zhí)行的是鑒權(quán)器組 unionAuthzHandler 的 Authorize 方法。在 unionAuthzHandler.Authorize 內(nèi)遍歷執(zhí)行每種鑒權(quán)器的 Authorize 方法,如果有一種鑒權(quán)器鑒權(quán)通過(guò),則返回鑒權(quán)成功。如果鑒權(quán)器返回 authorizer.DecisionNoOpinion 則執(zhí)行下一個(gè)鑒權(quán)器。如果鑒權(quán)器鑒權(quán)失敗則返回鑒權(quán)失敗。
1.3 authorization rules
前面介紹 alwaysAllowAuthorizer 鑒權(quán)器的時(shí)候我們看到 alwaysAllowAuthorizer.RulesFor 方法,該方法內(nèi)定義了用戶可以訪問(wèn)的 RESTful API 資源和非 RESTful API 資源。如 RESTful API 資源定義了訪問(wèn)資源的動(dòng)作 Verbs,資源組APIGroups 和資源 Resources。
上例的 alwaysAllowAuthorizer 并沒(méi)有看出 RulesFor 的運(yùn)用是因?yàn)?alwaysAllowAuthorizer 總是允許請(qǐng)求訪問(wèn) RESTful API 資源和非 RESTful API 資源。
我們以 rbacAuthorizer 鑒權(quán)器為例,看 RulesFor 是怎么被用上的。
func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
	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.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)
        }
    }
}
func New(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister) *RBACAuthorizer {
	authorizer := &RBACAuthorizer{
		authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver(
			roles, roleBindings, clusterRoles, clusterRoleBindings,
		),
	}
	return authorizer
}
func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
	ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes}
	r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit)
	if ruleCheckingVisitor.allowed {
		return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil
	}
}
可以看到,rbacAuthorizer 鑒權(quán)器的 Authorize 方法內(nèi)的 r.authorizationRuleResolver.VisitRulesFor 結(jié)合用戶信息和鑒權(quán)器的 rules 判斷鑒權(quán)是否通過(guò)。
2. 總結(jié)
通過(guò)本篇文章介紹了 kube-apiserver 中的 Authorization 鑒權(quán)流程,下一篇將繼續(xù)介紹 kube-apiserver 的 Adimission 準(zhǔn)入流程。
總結(jié)
以上是生活随笔為你收集整理的Kubernetes:kube-apiserver 之鉴权的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
                            
                        - 上一篇: 我们现代人饮苦食毒啊
 - 下一篇: 美国 iPhone 用户画像:Pro 机