Kubernetes源码学习-APIServer-P3-APIServer的认证机制

一时失言乱红尘 2022-10-27 02:37 460阅读 0赞

前言

在上一篇APIServer-P2-启动流程中,把APIServer预启动、加载配置、启动 的流程大致过了一遍,其中也有提到,APIServer的认证机制分为多种,以组合的形式遍历认证,任一机制认证成功,则判定请求认证成功,顺利进入下一个授权判定的环节。那么在本篇,就来详细看看这些认证机制。

Kubernetes用户

所有 Kubernetes 集群都有两类用户:由 Kubernetes 管理的服务账号普通用户

其中服务账号(ServiceAccount)是提供给集群中的程序使用,以Secret资源保存凭据,挂载到pod中,从而允许集群内的服务调用k8s API。

而普通用户,尚不支持使用API创建,一般由证书创建,Kubernetes 使用证书中的 ‘subject’ 的通用名称(Common Name)字段(例如,”/CN=bob”)来 确定用户名。

以上摘自官方文档

身份认证策略

Kubernetes 使用身份认证插件利用客户端证书、持有者令牌(Bearer Token)、身份认证代理(Proxy) 或者 HTTP 基本认证机制来认证 API 请求的身份。HTTP 请求发给 API 服务器时, 插件会将以下属性关联到请求本身:

  • 用户名:用来辩识最终用户的字符串。常见的值可以是 kube-adminjane@example.com
  • 用户 ID:用来辩识最终用户的字符串,旨在比用户名有更好的一致性和唯一性。
  • 用户组:取值为一组字符串,其中各个字符串用来标明用户是某个命名的用户逻辑集合的成员。 常见的值可能是 system:masters 或者 devops-team 等。
  • 附加字段:一组额外的键-值映射,键是字符串,值是一组字符串;用来保存一些鉴权组件可能 觉得有用的额外信息。

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

以上摘自官方文档

认证机制

流程图

首先为了加深印象,再贴一下上一章列出的认证流程图:

39ce31763229e400871952532dd8e647.png

注:认证器的执行顺序为随机的,图中流向不代表固定顺序

当客户端发送请求到达APIServer端,请求首先进入认证环节,对应处理认证的是Authentication Handler方法,

Authentication Handler方法中,遍历每一个已启用的认证器,仅需某一个认证器返回true,则认证成功结束遍历,若所有认证器全部返回false,则认证失败。

代码路径:

staging/src/k8s.io/apiserver/pkg/server/config.go:550

–> vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go:53

  1. func WithAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler, apiAuds authenticator.Audiences) http.Handler {
  2. // 关闭认证则直接进入下一个环节
  3. if auth == nil {
  4. klog.Warningf("Authentication is disabled")
  5. return handler
  6. }
  7. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  8. if len(apiAuds) > 0 {
  9. req = req.WithContext(authenticator.WithAudiences(req.Context(), apiAuds))
  10. }
  11. // 这里进行认证
  12. resp, ok, err := auth.AuthenticateRequest(req)
  13. if err != nil || !ok {
  14. if err != nil {
  15. klog.Errorf("Unable to authenticate the request due to an error: %v", err)
  16. }
  17. failed.ServeHTTP(w, req)
  18. return
  19. }
  20. // TODO(mikedanese): verify the response audience matches one of apiAuds if
  21. // non-empty
  22. // authorization header is not required anymore in case of a successful authentication.
  23. req.Header.Del("Authorization")
  24. // 请求上下文携带上认证后的用户的信息
  25. req = req.WithContext(genericapirequest.WithUser(req.Context(), resp.User))
  26. // 计数器
  27. authenticatedUserCounter.WithLabelValues(compressUsername(resp.User.GetName())).Inc()
  28. // 处理请求
  29. handler.ServeHTTP(w, req)
  30. })
  31. }

auth.AuthenticateRequest是一个接口方法,看看它的引用:

vendor/k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go:35

cf69ad705bc9aea38012ae1cdaefc48b.png

引用中的union.go中,聚合了所有的Authenticator链,进去看看

staging/src/k8s.io/apiserver/pkg/authentication/request/union/union.go:53

  1. // AuthenticateRequest authenticates the request using a chain of authenticator.Request objects.
  2. func (authHandler *unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
  3. var errlist []error
  4. for _, currAuthRequestHandler := range authHandler.Handlers {
  5. resp, ok, err := currAuthRequestHandler.AuthenticateRequest(req)
  6. if err != nil {
  7. if authHandler.FailOnError {
  8. return resp, ok, err
  9. }
  10. errlist = append(errlist, err)
  11. continue
  12. }
  13. if ok {
  14. return resp, ok, err
  15. }
  16. }
  17. return nil, false, utilerrors.NewAggregate(errlist)
  18. }

apiserver的各种request相关定义代码大多集中在这个目录里:

20210202145632.png

那么依次逐个来看看每一种Authenticator对应的AuthenticateRequest方法。

RequestHeader认证

是一种代理认证方式,需要再apiserver启动时以参数形式配置,来看看官方的介绍:

API 服务器可以配置成从请求的头部字段值(如 X-Remote-User)中辩识用户。 这一设计是用来与某身份认证代理一起使用 API 服务器,代理负责设置请求的头部字段值。

  • --requestheader-username-headers 必需字段,大小写不敏感。用来设置要获得用户身份所要检查的头部字段名称列表(有序)。第一个包含数值的字段会被用来提取用户名。
  • --requestheader-group-headers 可选字段,在 Kubernetes 1.6 版本以后支持,大小写不敏感。 建议设置为 “X-Remote-Group”。用来指定一组头部字段名称列表,以供检查用户所属的组名称。 所找到的全部头部字段的取值都会被用作用户组名。
  • --requestheader-extra-headers-prefix 可选字段,在 Kubernetes 1.6 版本以后支持,大小写不敏感。 建议设置为 “X-Remote-Extra-”。用来设置一个头部字段的前缀字符串,API 服务器会基于所给 前缀来查找与用户有关的一些额外信息。这些额外信息通常用于所配置的鉴权插件。 API 服务器会将与所给前缀匹配的头部字段过滤出来,去掉其前缀部分,将剩余部分 转换为小写字符串并在必要时执行百分号解码 后,构造新的附加信息字段键名。原来的头部字段值直接作为附加信息字段的值。

例如,使用下面的配置:

  1. --requestheader-username-headers=X-Remote-User
  2. --requestheader-group-headers=X-Remote-Group
  3. --requestheader-extra-headers-prefix=X-Remote-Extra-

针对所收到的如下请求:

  1. GET / HTTP/1.1
  2. X-Remote-User: fido
  3. X-Remote-Group: dogs
  4. X-Remote-Group: dachshunds
  5. X-Remote-Extra-Acme.com%2Fproject: some-project
  6. X-Remote-Extra-Scopes: openid
  7. X-Remote-Extra-Scopes: profile

会生成下面的用户信息:

  1. name: fido
  2. groups:
  3. - dogs
  4. - dachshunds
  5. extra:
  6. acme.com/project:
  7. - some-project
  8. scopes:
  9. - openid
  10. - profile

为了防范头部信息侦听,在请求中的头部字段被检视之前, 身份认证代理需要向 API 服务器提供一份合法的客户端证书, 供后者使用所给的 CA 来执行验证。 警告:不要 在不同的上下文中复用 CA 证书,除非你清楚这样做的风险是什么以及 应如何保护 CA 用法的机制。

  • --requestheader-client-ca-file 必需字段,给出 PEM 编码的证书包。 在检查请求的头部字段以提取用户名信息之前,必须提供一个合法的客户端证书, 且该证书要能够被所给文件中的机构所验证。
  • --requestheader-allowed-names 可选字段,用来给出一组公共名称(CN)。 如果此标志被设置,则在检视请求中的头部以提取用户信息之前,必须提供 包含此列表中所给的 CN 名的、合法的客户端证书。

以上摘自官方文档身份认证代理

简而言之,就是在apiserver之前有一个代理服务器,通过代理服务器的名义,可以将相关的认证信息在请求头透传给apiserver,以通过apiserver的认证。代理服务的相关信息需要预先配置。

使用kubeadm默认部署的集群,就启用了requestheader的配置:

077dc453e5f38d0f62467dc8a4fb736d.png

来看看AuthenticateRequest方法的代码:

vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader.go:108

  1. func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
  2. // 取到用户名
  3. name := headerValue(req.Header, a.nameHeaders)
  4. if len(name) == 0 {
  5. return nil, false, nil
  6. }
  7. // 取到用户group
  8. groups := allHeaderValues(req.Header, a.groupHeaders)
  9. // 取到其他信息
  10. extra := newExtra(req.Header, a.extraHeaderPrefixes)
  11. // clear headers used for authentication
  12. // 请求的header信息去除
  13. for _, headerName := range a.nameHeaders {
  14. req.Header.Del(headerName)
  15. }
  16. for _, headerName := range a.groupHeaders {
  17. req.Header.Del(headerName)
  18. }
  19. for k := range extra {
  20. for _, prefix := range a.extraHeaderPrefixes {
  21. req.Header.Del(prefix + k)
  22. }
  23. }
  24. // 返回认证成功信息
  25. return &authenticator.Response{
  26. User: &user.DefaultInfo{
  27. Name: name,
  28. Groups: groups,
  29. Extra: extra,
  30. },
  31. }, true, nil
  32. }

参考代码片中注释

BasicAuth认证

BasicAuth是一种简单的基础http认证,用户名、密码写入http请求头中,用base64编码,防君子不防小人,安全性较低,因此很少使用。快速略过

启动apiserver时,使用–basic-auth-file参数指定csv文件,csv里面以逗号切割,存放用户名、密码、uid。

看看AuthenticateRequest方法:

vendor/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth/basicauth.go:38

  1. // AuthenticateRequest authenticates the request using the "Authorization: Basic" header in the request
  2. func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
  3. username, password, found := req.BasicAuth()
  4. if !found {
  5. return nil, false, nil
  6. }
  7. // 简单的账号密码比对,没有什么值得深入的
  8. resp, ok, err := a.auth.AuthenticatePassword(req.Context(), username, password)
  9. // If the password authenticator didn't error, provide a default error
  10. if !ok && err == nil {
  11. err = errInvalidAuth
  12. }
  13. return resp, ok, err
  14. }

x509 CA认证

又称TLS双向认证,APIServer启动时使用–client-ca-file指定客户端的证书文件,用作请求的认证。

vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go:88

  1. // AuthenticateRequest authenticates the request using presented client certificates
  2. func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
  3. if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
  4. return nil, false, nil
  5. }
  6. // Use intermediates, if provided
  7. optsCopy := a.opts
  8. if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
  9. optsCopy.Intermediates = x509.NewCertPool()
  10. for _, intermediate := range req.TLS.PeerCertificates[1:] {
  11. optsCopy.Intermediates.AddCert(intermediate)
  12. }
  13. }
  14. remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now())
  15. clientCertificateExpirationHistogram.Observe(remaining.Seconds())
  16. // 校验客户端request携带的证书,如果是ca签名的证书,且在有效期内,则视为有效,返回对应的证书chain
  17. chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
  18. if err != nil {
  19. return nil, false, err
  20. }
  21. var errlist []error
  22. for _, chain := range chains {
  23. // 这里再展开一下
  24. user, ok, err := a.user.User(chain)
  25. if err != nil {
  26. errlist = append(errlist, err)
  27. continue
  28. }
  29. if ok {
  30. return user, ok, err
  31. }
  32. }
  33. return nil, false, utilerrors.NewAggregate(errlist)
  34. }

–> vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go:72

–> vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go:185

  1. // CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName
  2. var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
  3. if len(chain[0].Subject.CommonName) == 0 {
  4. return nil, false, nil
  5. }
  6. // 取得证书chain中的CommonName/Organization作为用户和组
  7. return &authenticator.Response{
  8. User: &user.DefaultInfo{
  9. Name: chain[0].Subject.CommonName,
  10. Groups: chain[0].Subject.Organization,
  11. },
  12. }, true, nil
  13. })

BearerToken认证

这种认证方式是专为k8s节点准备的,避免每个节点都要手动配置TLS证书,在apiserver启动时指定--enable-bootstrap-token-auth参数来启用这种认证方式,详细说明见官方文档:

启动引导令牌

bearertoken的认证方式是在请求头里放入bearer令牌,令牌的格式为 [a-z0-9]{6}.[a-z0-9]{16}。第一个部分是令牌的 ID;第二个部分 是令牌的 Secret。对应http请求头的格式是:

Authorization: Bearer xxxxxx.xxxxxxxxxxxxxxxx

代码路径:

vendor/k8s.io/apiserver/pkg/authentication/request/bearertoken/bearertoken.go:37

  1. func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
  2. // Authorization header
  3. auth := strings.TrimSpace(req.Header.Get("Authorization"))
  4. if auth == "" {
  5. return nil, false, nil
  6. }
  7. // 切割分开,第二部分就是token
  8. parts := strings.Split(auth, " ")
  9. if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
  10. return nil, false, nil
  11. }
  12. token := parts[1]
  13. // Empty bearer tokens aren't valid
  14. if len(token) == 0 {
  15. return nil, false, nil
  16. }
  17. // 校验token
  18. resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
  19. // if we authenticated successfully, go ahead and remove the bearer token so that no one
  20. // is ever tempted to use it inside of the API server
  21. if ok {
  22. req.Header.Del("Authorization")
  23. }
  24. // If the token authenticator didn't error, provide a default error
  25. if !ok && err == nil {
  26. err = invalidToken
  27. }
  28. return resp, ok, err
  29. }

AuthenticateToken是一个接口方法:

vendor/k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go:28

  1. type Token interface {
  2. AuthenticateToken(ctx context.Context, token string) (*Response, bool, error)
  3. }
  4. // TokenFunc is a function that implements the Token interface.
  5. type TokenFunc func(ctx context.Context, token string) (*Response, bool, error)
  6. // AuthenticateToken implements authenticator.Token.
  7. func (f TokenFunc) AuthenticateToken(ctx context.Context, token string) (*Response, bool, error) {
  8. return f(ctx, token)
  9. }

接口的实现方法在:

plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go:94

  1. func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
  2. // 把token用'.'切割分为id和密文两段
  3. tokenID, tokenSecret, err := parseToken(token)
  4. if err != nil {
  5. // Token isn't of the correct form, ignore it.
  6. return nil, false, nil
  7. }
  8. // 从APIServer取到对应的secret资源对象,跟这个token进行信息比对
  9. secretName := bootstrapapi.BootstrapTokenSecretPrefix + tokenID
  10. secret, err := t.lister.Get(secretName)
  11. if err != nil {
  12. if errors.IsNotFound(err) {
  13. klog.V(3).Infof("No secret of name %s to match bootstrap bearer token", secretName)
  14. return nil, false, nil
  15. }
  16. return nil, false, err
  17. }
  18. if secret.DeletionTimestamp != nil {
  19. tokenErrorf(secret, "is deleted and awaiting removal")
  20. return nil, false, nil
  21. }
  22. if string(secret.Type) != string(bootstrapapi.SecretTypeBootstrapToken) || secret.Data == nil {
  23. tokenErrorf(secret, "has invalid type, expected %s.", bootstrapapi.SecretTypeBootstrapToken)
  24. return nil, false, nil
  25. }
  26. ts := getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
  27. if subtle.ConstantTimeCompare([]byte(ts), []byte(tokenSecret)) != 1 {
  28. tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenSecretKey, tokenSecret)
  29. return nil, false, nil
  30. }
  31. id := getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
  32. if id != tokenID {
  33. tokenErrorf(secret, "has invalid value for key %s, expected %s.", bootstrapapi.BootstrapTokenIDKey, tokenID)
  34. return nil, false, nil
  35. }
  36. if isSecretExpired(secret) {
  37. // logging done in isSecretExpired method.
  38. return nil, false, nil
  39. }
  40. if getSecretString(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
  41. tokenErrorf(secret, "not marked %s=true.", bootstrapapi.BootstrapTokenUsageAuthentication)
  42. return nil, false, nil
  43. }
  44. groups, err := getGroups(secret)
  45. if err != nil {
  46. tokenErrorf(secret, "has invalid value for key %s: %v.", bootstrapapi.BootstrapTokenExtraGroupsKey, err)
  47. return nil, false, nil
  48. }
  49. return &authenticator.Response{
  50. User: &user.DefaultInfo{
  51. Name: bootstrapapi.BootstrapUserPrefix + string(id),
  52. Groups: groups,
  53. },
  54. }, true, nil
  55. }

ServiceAccount(SA)认证

启用方式为APIServer命令使用--service-account-key-file参数指定一个为token签名的PEM秘钥文件。

SA认证是jwt形式的认证,使用方式与bearer token类似,也是放在请求头里,内容为Base64编码,header格式为:

Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo0NTk0LCJ1c2VybmFtZSI6InlpbndlbnFpbiIsImV4cCI6MTU3MDY3NDEyNywiZW1haWwiOiIifQ.djC2w5l3IiXYv7slZtGzlMzLc3_oPuR1M0dM9FwoaUU

token哪里来呢?答案就是ServiceAccount。

SA是一种面向集群内部应用需要调用APIServer的场景所设计的认证方式。在创建ServiceAccount资源时,可以显示地设置标签将ServiceAccount绑定给某Deploy/sts/pod,也可以在Deploy/sts/pod的声明文件里显示指定ServiceAccount。ServiceAccount会自动创建Secret资源,token秘钥存放其中。

在相应的容器层面,token信息会被挂载进容器中,包含3个文件:

  • namespace文件:指明命名空间
  • ca.crt文件:APIServer的公钥证书,容器用来校验APIServer
  • token文件: 存放在Secret里的JWT token

在集群上找一个例子,以kube-proxy举例:

13d59f8a3d6bd54446a901f38e2ff817.png

看看代码层面:

pkg/serviceaccount/jwt.go:145

  1. func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) {
  2. if !j.hasCorrectIssuer(tokenData) {
  3. return nil, false, nil
  4. }
  5. // 拿到token
  6. tok, err := jwt.ParseSigned(tokenData)
  7. if err != nil {
  8. return nil, false, nil
  9. }
  10. public := &jwt.Claims{ }
  11. private := j.validator.NewPrivateClaims()
  12. var (
  13. found bool
  14. errlist []error
  15. )
  16. for _, key := range j.keys {
  17. if err := tok.Claims(key, public, private); err != nil {
  18. errlist = append(errlist, err)
  19. continue
  20. }
  21. found = true
  22. break
  23. }
  24. if !found {
  25. return nil, false, utilerrors.NewAggregate(errlist)
  26. }
  27. tokenAudiences := authenticator.Audiences(public.Audience)
  28. if len(tokenAudiences) == 0 {
  29. // only apiserver audiences are allowed for legacy tokens
  30. tokenAudiences = j.implicitAuds
  31. }
  32. requestedAudiences, ok := authenticator.AudiencesFrom(ctx)
  33. if !ok {
  34. // default to apiserver audiences
  35. requestedAudiences = j.implicitAuds
  36. }
  37. auds := authenticator.Audiences(tokenAudiences).Intersect(requestedAudiences)
  38. if len(auds) == 0 && len(j.implicitAuds) != 0 {
  39. return nil, false, fmt.Errorf("token audiences %q is invalid for the target audiences %q", tokenAudiences, requestedAudiences)
  40. }
  41. // If we get here, we have a token with a recognized signature and
  42. // issuer string.
  43. // 校验token并拿到SA信息
  44. sa, err := j.validator.Validate(tokenData, public, private)
  45. if err != nil {
  46. return nil, false, err
  47. }
  48. return &authenticator.Response{
  49. User: sa.UserInfo(),
  50. Audiences: auds,
  51. }, true, nil
  52. }

WebhookToken认证

Webhook 身份认证是一种用来验证持有者令牌的回调机制。

  • --authentication-token-webhook-config-file 指向一个配置文件,其中描述 如何访问远程的 Webhook 服务。
  • --authentication-token-webhook-cache-ttl 用来设定身份认证决定的缓存时间。 默认时长为 2 分钟。

配置文件使用 kubeconfig 文件的格式。文件中,clusters 指代远程服务,users 指代远程 API 服务 Webhook。下面是一个例子:

  1. # Kubernetes API 版本
  2. apiVersion: v1
  3. # API 对象类别
  4. kind: Config
  5. # clusters 指代远程服务
  6. clusters:
  7. - name: name-of-remote-authn-service
  8. cluster:
  9. certificate-authority: /path/to/ca.pem # 用来验证远程服务的 CA
  10. server: https://authn.example.com/authenticate # 要查询的远程服务 URL。必须使用 'https'。
  11. # users 指代 API 服务的 Webhook 配置
  12. users:
  13. - name: name-of-api-server
  14. user:
  15. client-certificate: /path/to/cert.pem # Webhook 插件要使用的证书
  16. client-key: /path/to/key.pem # 与证书匹配的密钥
  17. # kubeconfig 文件需要一个上下文(Context),此上下文用于本 API 服务器
  18. current-context: webhook
  19. contexts:
  20. - context:
  21. cluster: name-of-remote-authn-service
  22. user: name-of-api-sever
  23. name: webhook

当客户端尝试在 API 服务器上使用持有者令牌完成身份认证( 如前所述)时, 身份认证 Webhook 会用 POST 请求发送一个 JSON 序列化的对象到远程服务。 该对象是 authentication.k8s.io/v1beta1 组的 TokenReview 对象, 其中包含持有者令牌。 Kubernetes 不会强制请求提供此 HTTP 头部。

要注意的是,Webhook API 对象和其他 Kubernetes API 对象一样,也要受到同一 版本兼容规则约束。 实现者要了解对 Beta 阶段对象的兼容性承诺,并检查请求的 apiVersion 字段, 以确保数据结构能够正常反序列化解析。此外,API 服务器必须启用 authentication.k8s.io/v1beta1 API 扩展组 (--runtime-config=authentication.k8s.io/v1beta1=true)。

POST 请求的 Body 部分将是如下格式:

  1. {
  2. "apiVersion": "authentication.k8s.io/v1beta1",
  3. "kind": "TokenReview",
  4. "spec": {
  5. "token": "<持有者令牌>"
  6. }
  7. }

远程服务应该会填充请求的 status 字段,以标明登录操作是否成功。 响应的 Body 中的 spec 字段会被忽略,因此可以省略。 如果持有者令牌验证成功,应该返回如下所示的响应:

  1. {
  2. "apiVersion": "authentication.k8s.io/v1beta1",
  3. "kind": "TokenReview",
  4. "status": {
  5. "authenticated": true,
  6. "user": {
  7. "username": "janedoe@example.com",
  8. "uid": "42",
  9. "groups": [
  10. "developers",
  11. "qa"
  12. ],
  13. "extra": {
  14. "extrafield1": [
  15. "extravalue1",
  16. "extravalue2"
  17. ]
  18. }
  19. }
  20. }
  21. }

而不成功的请求会返回:

  1. {
  2. "apiVersion": "authentication.k8s.io/v1beta1",
  3. "kind": "TokenReview",
  4. "status": {
  5. "authenticated": false
  6. }
  7. }

HTTP 状态码可用来提供进一步的错误语境信息。

以上摘自官方文档Webhook 令牌身份认证

代码:

vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/webhook.go:77

  1. // AuthenticateToken implements the authenticator.Token interface.
  2. func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
  3. // We take implicit audiences of the API server at WebhookTokenAuthenticator
  4. // construction time. The outline of how we validate audience here is:
  5. //
  6. // * if the ctx is not audience limited, don't do any audience validation.
  7. // * if ctx is audience-limited, add the audiences to the tokenreview spec
  8. // * if the tokenreview returns with audiences in the status that intersect
  9. // with the audiences in the ctx, copy into the response and return success
  10. // * if the tokenreview returns without an audience in the status, ensure
  11. // the ctx audiences intersect with the implicit audiences, and set the
  12. // intersection in the response.
  13. // * otherwise return unauthenticated.
  14. wantAuds, checkAuds := authenticator.AudiencesFrom(ctx)
  15. r := &authentication.TokenReview{
  16. Spec: authentication.TokenReviewSpec{
  17. Token: token,
  18. Audiences: wantAuds,
  19. },
  20. }
  21. var (
  22. result *authentication.TokenReview
  23. err error
  24. auds authenticator.Audiences
  25. )
  26. webhook.WithExponentialBackoff(w.initialBackoff, func() error {
  27. // webhook插件向远端认证服务器发起post请求
  28. result, err = w.tokenReview.Create(r)
  29. return err
  30. })
  31. if err != nil {
  32. // An error here indicates bad configuration or an outage. Log for debugging.
  33. klog.Errorf("Failed to make webhook authenticator request: %v", err)
  34. return nil, false, err
  35. }
  36. if checkAuds {
  37. gotAuds := w.implicitAuds
  38. if len(result.Status.Audiences) > 0 {
  39. gotAuds = result.Status.Audiences
  40. }
  41. auds = wantAuds.Intersect(gotAuds)
  42. if len(auds) == 0 {
  43. return nil, false, nil
  44. }
  45. }
  46. r.Status = result.Status
  47. if !r.Status.Authenticated {
  48. var err error
  49. if len(r.Status.Error) != 0 {
  50. err = errors.New(r.Status.Error)
  51. }
  52. return nil, false, err
  53. }
  54. var extra map[string][]string
  55. if r.Status.User.Extra != nil {
  56. extra = map[string][]string{ }
  57. for k, v := range r.Status.User.Extra {
  58. extra[k] = v
  59. }
  60. }
  61. // 根据远端认证服务器返回的status字段,填充请求的相关用户信息(name/uid/group等)
  62. return &authenticator.Response{
  63. User: &user.DefaultInfo{
  64. Name: r.Status.User.Username,
  65. UID: r.Status.User.UID,
  66. Groups: r.Status.User.Groups,
  67. Extra: extra,
  68. },
  69. Audiences: auds,
  70. }, true, nil
  71. }

Anonymous认证

启用匿名请求支持之后,如果请求没有被已配置的其他身份认证方法拒绝,则被视作 匿名请求(Anonymous Requests)。这类请求获得用户名 system:anonymous 和 对应的用户组 system:unauthenticated

例如,在一个配置了令牌身份认证且启用了匿名访问的服务器上,如果请求提供了非法的 持有者令牌,则会返回 401 Unauthorized 错误。 如果请求没有提供持有者令牌,则被视为匿名请求。

在 1.5.1-1.5.x 版本中,匿名访问默认情况下是被禁用的,可以通过为 API 服务器设定 --anonymous-auth=true 来启用。

在 1.6 及之后版本中,如果所使用的鉴权模式不是 AlwaysAllow,则匿名访问默认是被启用的。 从 1.6 版本开始,ABAC 和 RBAC 鉴权模块要求对 system:anonymous 用户或者 system:unauthenticated 用户组执行显式的权限判定,所以之前的为 * 用户或 * 用户组赋予访问权限的策略规则都不再包含匿名用户。

以上摘自官方文档匿名请求

代码:

vendor/k8s.io/apiserver/pkg/authentication/request/anonymous/anonymous.go:32

  1. func NewAuthenticator() authenticator.Request {
  2. return authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
  3. auds, _ := authenticator.AudiencesFrom(req.Context())
  4. return &authenticator.Response{
  5. User: &user.DefaultInfo{
  6. Name: anonymousUser,
  7. Groups: []string{ unauthenticatedGroup},
  8. },
  9. Audiences: auds,
  10. }, true, nil
  11. })
  12. }

总结

认证机制有许多种,每种都有不同的适用场景,一般情况下只需要了解认证流程和其中常见的几种认证器即可,不必过分深挖。认证流程通过之后,下一篇进入授权环节

发表评论

表情:
评论列表 (有 0 条评论,460人围观)

还没有评论,来说两句吧...

相关阅读