Kubernetes-apiServer源码深度分析(十二)Authentication & Authorization

1,268 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

记得看代码中的注释哈,理解都在里面 源码基于v1.24

本篇文章主要介绍kube-apiServer的认证和鉴权部分的代码,了解其加载流程和执行流程,理清全流程

登录和鉴权

1.Authentication(认证):认证方式现共有8种,可以启用一种或多种认证方式,只要有一种认证方式通过,就不再 进行其它方式的认证。通常启用X509 Client Certs和Service Accout Tokens两种认证方式。 Kubernetes集群有两类用户:由Kubernetes管理的Service Accounts (服务账户)和 (Users Accounts) 普通账户。 k8s中账号的概念不是我们理解的账号,它并不真的存在, 它只是形式上存在。

2.Authorization(授权):必须经过认证阶段,才到授权请求,根据所有授权策略匹配请求资源属性,决定允许或拒 绝请求。授权方式现共有6种,AlwaysDeny、AlwaysAllow、ABAC、RBAC、Webhook、 Node。默认集群强制开启RBAC。

image.png 再看请求的转发过程,登录鉴权只发生在Aggregator image.png

转发到Delegate

image.png

转发到Proxy

image.png

实现和加载

我们举一个x509认证的例子

func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
   var authenticators []authenticator.Request
   var tokenAuthenticators []authenticator.Token
   securityDefinitions := spec.SecurityDefinitions{}

   if config.RequestHeaderConfig != nil {
      requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
         config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
         config.RequestHeaderConfig.AllowedClientNames,
         config.RequestHeaderConfig.UsernameHeaders,
         config.RequestHeaderConfig.GroupHeaders,
         config.RequestHeaderConfig.ExtraHeaderPrefixes,
      )
      authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
   }

   // X509 methods
   if config.ClientCAContentProvider != nil {
      certAuth := x509.NewDynamic(config.ClientCAContentProvider.VerifyOptions, x509.CommonNameUserConversion)
      authenticators = append(authenticators, certAuth)
   }

   // Bearer token methods, local first, then remote
   if len(config.TokenAuthFile) > 0 {
      tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
      if err != nil {
         return nil, nil, err
      }
      tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
   }
   if len(config.ServiceAccountKeyFiles) > 0 {
      serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter)
      if err != nil {
         return nil, nil, err
      }
      tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
   }
   if len(config.ServiceAccountIssuers) > 0 {
      serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuers, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
      if err != nil {
         return nil, nil, err
      }
      tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
   }
   if config.BootstrapToken {
      if config.BootstrapTokenAuthenticator != nil {

         tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, config.BootstrapTokenAuthenticator))
      }
   }

   if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
      // TODO(enj): wire up the Notifier and ControllerRunner bits when OIDC supports CA reload
      var oidcCAContent oidc.CAContentProvider
      if len(config.OIDCCAFile) != 0 {
         var oidcCAErr error
         oidcCAContent, oidcCAErr = dynamiccertificates.NewDynamicCAContentFromFile("oidc-authenticator", config.OIDCCAFile)
         if oidcCAErr != nil {
            return nil, nil, oidcCAErr
         }
      }
/*
    openID的验证
*/
      oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(oidc.Options{
         IssuerURL:            config.OIDCIssuerURL,
         ClientID:             config.OIDCClientID,
         CAContentProvider:    oidcCAContent,
         UsernameClaim:        config.OIDCUsernameClaim,
         UsernamePrefix:       config.OIDCUsernamePrefix,
         GroupsClaim:          config.OIDCGroupsClaim,
         GroupsPrefix:         config.OIDCGroupsPrefix,
         SupportedSigningAlgs: config.OIDCSigningAlgs,
         RequiredClaims:       config.OIDCRequiredClaims,
      })
      if err != nil {
         return nil, nil, err
      }
      tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, oidcAuth))
   }
   if len(config.WebhookTokenAuthnConfigFile) > 0 {
      webhookTokenAuth, err := newWebhookTokenAuthenticator(config)
      if err != nil {
         return nil, nil, err
      }

      tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
   }

   if len(tokenAuthenticators) > 0 {
      // Union the token authenticators
      tokenAuth := tokenunion.New(tokenAuthenticators...)
      // Optionally cache authentication results
      if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
         tokenAuth = tokencache.New(tokenAuth, true, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
      }
      authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
      securityDefinitions["BearerToken"] = &spec.SecurityScheme{
         SecuritySchemeProps: spec.SecuritySchemeProps{
            Type:        "apiKey",
            Name:        "authorization",
            In:          "header",
            Description: "Bearer Token authentication",
         },
      }
   }

   if len(authenticators) == 0 {
      if config.Anonymous {
         return anonymous.NewAuthenticator(), &securityDefinitions, nil
      }
      return nil, &securityDefinitions, nil
   }

   authenticator := union.New(authenticators...)

   authenticator = group.NewAuthenticatedGroupAdder(authenticator)

   if config.Anonymous {
      authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
   }

   return authenticator, &securityDefinitions, nil
}
/*
    staging\src\k8s.io\apiserver\pkg\authentication\request\x509\x509.go
*/

func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
   if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
      return nil, false, nil
   }

   // Use intermediates, if provided
   optsCopy, ok := a.verifyOptionsFn()
   // if there are intentionally no verify options, then we cannot authenticate this request
   if !ok {
      return nil, false, nil
   }
   if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
      optsCopy.Intermediates = x509.NewCertPool()
      for _, intermediate := range req.TLS.PeerCertificates[1:] {
         optsCopy.Intermediates.AddCert(intermediate)
      }
   }

   remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now())
   clientCertificateExpirationHistogram.WithContext(req.Context()).Observe(remaining.Seconds())
   chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
   if err != nil {
      return nil, false, fmt.Errorf(
         "verifying certificate %s failed: %w",
         certificateIdentifier(req.TLS.PeerCertificates[0]),
         err,
      )
   }
/*
    比较优雅的错误处理方式,先把err存起来
*/
   var errlist []error
   for _, chain := range chains {
      user, ok, err := a.user.User(chain)
      if err != nil {
         errlist = append(errlist, err)
         continue
      }

      if ok {
         return user, ok, err
      }
   }
   return nil, false, utilerrors.NewAggregate(errlist)
}

流程解析如下图 image.png 代码量比较多,有兴趣可以去仔细看看 image.png

Summary

Authentication & Authorization 是每一个请求必经的过程,由于请求又是由Aggregator来转发的,所以在Aggregator就完成了认证鉴权的操作,这种委托的机制提高了验证的效率