前言

上一篇文章介绍了 kube-apiserver 的认证机制。这里继续往下走,介绍 kube-apiserver 的鉴权。kube-apiserver 处理认证和鉴权非常类似,建议阅读鉴权机制前先看看 kube-apiserver认证

鉴权 Authorization

Authorization handler

进入 DefaultBuildHandlerChainAuthorization handler 创建过程。

 1# kubernetes/vendor/k8s.io/apiserver/pkg/server/config.go
 2func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
 3	handler := apiHandler
 4
 5	handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
 6	handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authorization")
 7
 8    handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences, c.Authentication.RequestHeaderConfig)
 9	handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authentication")
10}

这里 handler chainhandler 处理顺序是由下往上的,即处理完 authentication handler 在处理 authorization handler

进入 genericapifilters.WithAuthorization 查看鉴权 handler 的创建过程。

 1# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
 2func WithAuthorization(hhandler http.Handler, auth authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
 3	return withAuthorization(hhandler, auth, s, recordAuthorizationMetrics)
 4}
 5
 6func withAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer, metrics recordAuthorizationMetricsFunc) http.Handler {
 7	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 8		ctx := req.Context()
 9		authorizationStart := time.Now()
10
11        // 获取 request 携带的用户信息
12		attributes, err := GetAuthorizerAttributes(ctx)
13		if err != nil {
14			responsewriters.InternalError(w, req, err)
15			return
16		}
17
18        // 对用户信息进行鉴权
19		authorized, reason, err := a.Authorize(ctx, attributes)
20
21		...
22	})
23}

鉴权过程包括两部分。

一部分通过 GetAuthorizerAttributes 获取 RESTful API 请求中携带的用户信息。

 1# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
 2func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) {
 3	attribs := authorizer.AttributesRecord{}
 4
 5	user, ok := request.UserFrom(ctx)
 6	if ok {
 7		attribs.User = user
 8	}
 9
10	requestInfo, found := request.RequestInfoFrom(ctx)
11	if !found {
12		return nil, errors.New("no RequestInfo found in the context")
13	}
14
15	// Start with common attributes that apply to resource and non-resource requests
16	attribs.ResourceRequest = requestInfo.IsResourceRequest
17	attribs.Path = requestInfo.Path
18	attribs.Verb = requestInfo.Verb
19
20	attribs.APIGroup = requestInfo.APIGroup
21	attribs.APIVersion = requestInfo.APIVersion
22	attribs.Resource = requestInfo.Resource
23	attribs.Subresource = requestInfo.Subresource
24	attribs.Namespace = requestInfo.Namespace
25	attribs.Name = requestInfo.Name
26
27	return &attribs, nil
28}

获取到用户信息后通过 a.Authorize(ctx, attributes) 对用户及其请求进行鉴权。其中 a 是实现了 Authorizer 鉴权器接口的实例。

1type Authorizer interface {
2	Authorize(ctx context.Context, a Attributes) (authorized Decision, reason string, err error)
3}

查看 a.Authorize(ctx, attributes) 实际是看 Config.Authorization.Authorizer 中的实例。

1func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
2	handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
3}

Authorization authorizer

Config.Authorization.AuthorizerBuildGenericConfigBuildAuthorizer 函数内创建。

 1# kubernetes/pkg/controlplane/apiserver/config.go
 2func BuildGenericConfig(
 3	s controlplaneapiserver.CompletedOptions,
 4	schemes []*runtime.Scheme,
 5	getOpenAPIDefinitions func(ref openapicommon.ReferenceCallback) map[string]openapicommon.OpenAPIDefinition,
 6) (
 7	genericConfig *genericapiserver.Config,
 8	versionedInformers clientgoinformers.SharedInformerFactory,
 9	storageFactory *serverstorage.DefaultStorageFactory,
10
11	lastErr error,
12) {
13    genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers)
14	if err != nil {
15		lastErr = fmt.Errorf("invalid authorization config: %v", err)
16		return
17	}
18}

进入 BuildAuthorizer 查看 Authorizer 是怎么创建的。

 1# kubernetes/pkg/controlplane/apiserver/config.go
 2func BuildAuthorizer(s controlplaneapiserver.CompletedOptions, EgressSelector *egressselector.EgressSelector, versionedInformers clientgoinformers.SharedInformerFactory) (authorizer.Authorizer, authorizer.RuleResolver, error) {
 3	authorizationConfig := s.Authorization.ToAuthorizationConfig(versionedInformers)
 4
 5	if EgressSelector != nil {
 6		egressDialer, err := EgressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext())
 7		if err != nil {
 8			return nil, nil, err
 9		}
10		authorizationConfig.CustomDial = egressDialer
11	}
12
13	return authorizationConfig.New()
14}

创建 Authorizer 分为两块。首先创建 authorizationConfig,接着通过 authorizationConfig.New() 实例化 authorizer

 1# kubernetes/pkg/kubeapiserver/authorizer/config.go
 2func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
 3	var (
 4		authorizers   []authorizer.Authorizer
 5		ruleResolvers []authorizer.RuleResolver
 6	)
 7
 8	for _, authorizationMode := range config.AuthorizationModes {
 9		switch authorizationMode {
10		case modes.ModeNode:
11			...
12		case modes.ModeAlwaysAllow:
13			...
14		case modes.ModeAlwaysDeny:
15            ...
16		case modes.ModeABAC:
17			...
18		case modes.ModeWebhook:
19			...
20		case modes.ModeRBAC:
21			...
22		default:
23			return nil, nil, fmt.Errorf("unknown authorization mode %s specified", authorizationMode)
24		}
25	}
26
27	return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil
28}

可以看到,authorizationConfig.New() 内根据 config.AuthorizationModes 确定需要创建的鉴权器类型。这里有 modes.ModeNodemodes.ModeAlwaysAllowmodes.ModeAlwaysDenymodes.ModeABACmodes.ModeWebhookmodes.ModeRBAC 六种鉴权器。

config.AuthorizationModes 是在创建选项 NewOptions 中定义的,实例化过程如下:

 1func (o *BuiltInAuthorizationOptions) ToAuthorizationConfig(versionedInformerFactory versionedinformers.SharedInformerFactory) authorizer.Config {
 2	return authorizer.Config{
 3		AuthorizationModes:          o.Modes,
 4	}
 5}
 6
 7# kubernetes/pkg/controlplane/apiserver/options/options.go
 8func NewOptions() *Options {
 9	s := Options{
10		Authorization:           kubeoptions.NewBuiltInAuthorizationOptions()
11	}
12
13	return &s
14}
15
16# kubernetes/pkg/kubeapiserver/options/authorization.go
17func NewBuiltInAuthorizationOptions() *BuiltInAuthorizationOptions {
18	return &BuiltInAuthorizationOptions{
19		Modes:                       []string{authzmodes.ModeAlwaysAllow},
20		WebhookVersion:              "v1beta1",
21		WebhookCacheAuthorizedTTL:   5 * time.Minute,
22		WebhookCacheUnauthorizedTTL: 30 * time.Second,
23		WebhookRetryBackoff:         genericoptions.DefaultAuthWebhookRetryBackoff(),
24	}
25}

这里的 config.AuthorizationModesauthzmodes.ModeAlwaysAllow。那么,将创建 alwaysAllowAuthorizer 鉴权器。

 1# kubernetes/pkg/kubeapiserver/authorizer/config.go
 2func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
 3	var (
 4		authorizers   []authorizer.Authorizer
 5		ruleResolvers []authorizer.RuleResolver
 6	)
 7
 8	for _, authorizationMode := range config.AuthorizationModes {
 9		switch authorizationMode {
10		case modes.ModeAlwaysAllow:
11			alwaysAllowAuthorizer := authorizerfactory.NewAlwaysAllowAuthorizer()
12			authorizers = append(authorizers, alwaysAllowAuthorizer)
13			ruleResolvers = append(ruleResolvers, alwaysAllowAuthorizer)
14        }
15    }
16
17    return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil
18}
19
20# kubernetes/vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory/builtin.go
21func NewAlwaysAllowAuthorizer() *alwaysAllowAuthorizer {
22	return new(alwaysAllowAuthorizer)
23}
24
25type alwaysAllowAuthorizer struct{}
26
27func (alwaysAllowAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
28	return authorizer.DecisionAllow, "", nil
29}
30
31func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
32	return []authorizer.ResourceRuleInfo{
33			&authorizer.DefaultResourceRuleInfo{
34				Verbs:     []string{"*"},
35				APIGroups: []string{"*"},
36				Resources: []string{"*"},
37			},
38		}, []authorizer.NonResourceRuleInfo{
39			&authorizer.DefaultNonResourceRuleInfo{
40				Verbs:           []string{"*"},
41				NonResourceURLs: []string{"*"},
42			},
43		}, false, nil
44}

alwaysAllowAuthorizer 鉴权器实现了 Authorizer 接口,其总是返回 authorizer.DecisionAllow

每种鉴权器通过 union.New 加到鉴权器组中。

 1func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer {
 2	return unionAuthzHandler(authorizationHandlers)
 3}
 4
 5// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful
 6func (authzHandler unionAuthzHandler) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
 7	var (
 8		errlist    []error
 9		reasonlist []string
10	)
11
12	for _, currAuthzHandler := range authzHandler {
13		decision, reason, err := currAuthzHandler.Authorize(ctx, a)
14
15		if err != nil {
16			errlist = append(errlist, err)
17		}
18		if len(reason) != 0 {
19			reasonlist = append(reasonlist, reason)
20		}
21		switch decision {
22		case authorizer.DecisionAllow, authorizer.DecisionDeny:
23			return decision, reason, err
24		case authorizer.DecisionNoOpinion:
25			// continue to the next authorizer
26		}
27	}
28
29	return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
30}

前面 handlera.Authorize(ctx, attributes) 实际执行的是鉴权器组 unionAuthzHandlerAuthorize 方法。在 unionAuthzHandler.Authorize 内遍历执行每种鉴权器的 Authorize 方法,如果有一种鉴权器鉴权通过,则返回鉴权成功。如果鉴权器返回 authorizer.DecisionNoOpinion 则执行下一个鉴权器。如果鉴权器鉴权失败则返回鉴权失败。

authorization rules

前面介绍 alwaysAllowAuthorizer 鉴权器的时候我们看到 alwaysAllowAuthorizer.RulesFor 方法,该方法内定义了用户可以访问的 RESTful API 资源和非 RESTful API 资源。如 RESTful API 资源定义了访问资源的动作 Verbs,资源组APIGroups 和资源 Resources

上例的 alwaysAllowAuthorizer 并没有看出 RulesFor 的运用是因为 alwaysAllowAuthorizer 总是允许请求访问 RESTful API 资源和非 RESTful API 资源。

我们以 rbacAuthorizer 鉴权器为例,看 RulesFor 是怎么被用上的。

 1func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
 2	for _, authorizationMode := range config.AuthorizationModes {
 3		// Keep cases in sync with constant list in k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes/modes.go.
 4		switch authorizationMode {
 5            case modes.ModeRBAC:
 6			rbacAuthorizer := rbac.New(
 7				&rbac.RoleGetter{Lister: config.VersionedInformerFactory.Rbac().V1().Roles().Lister()},
 8				&rbac.RoleBindingLister{Lister: config.VersionedInformerFactory.Rbac().V1().RoleBindings().Lister()},
 9				&rbac.ClusterRoleGetter{Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoles().Lister()},
10				&rbac.ClusterRoleBindingLister{Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoleBindings().Lister()},
11			)
12			authorizers = append(authorizers, rbacAuthorizer)
13			ruleResolvers = append(ruleResolvers, rbacAuthorizer)
14        }
15    }
16}
17
18func New(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister) *RBACAuthorizer {
19	authorizer := &RBACAuthorizer{
20		authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver(
21			roles, roleBindings, clusterRoles, clusterRoleBindings,
22		),
23	}
24	return authorizer
25}
26
27func (r *RBACAuthorizer) Authorize(ctx context.Context, requestAttributes authorizer.Attributes) (authorizer.Decision, string, error) {
28	ruleCheckingVisitor := &authorizingVisitor{requestAttributes: requestAttributes}
29
30	r.authorizationRuleResolver.VisitRulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace(), ruleCheckingVisitor.visit)
31	if ruleCheckingVisitor.allowed {
32		return authorizer.DecisionAllow, ruleCheckingVisitor.reason, nil
33	}
34}

可以看到,rbacAuthorizer 鉴权器的 Authorize 方法内的 r.authorizationRuleResolver.VisitRulesFor 结合用户信息和鉴权器的 rules 判断鉴权是否通过。

总结

通过本篇文章介绍了 kube-apiserver 中的 Authorization 鉴权流程,下一篇将继续介绍 kube-apiserverAdimission 准入流程。