K8s-kube-apiserver(请求与认证)

基于1.25

kube-apiserver请求处理流程:

权限控制体系

认证

kube-apiserver支持多种认证机制,并且支持同时开启多个认证功能,组成认证链路

目前支持8种:

  • RequestHeader
  • ClientCA
  • TokenAuth
  • ServiceAccountAuht
  • BootstrrapToken
  • OIDC
  • WebHookTokenAuth
  • Anonymous

RequestHeader认证

kubeapiserver通过制定以下参数开启RequestHeader认证

  • --requestheader-client-ca-file制定认证代理客户端证书签发所使用的CA证书,必选
  • --requestheader-allowed-names:指定允许的通用名称(CN)列表。如果设置则客户端中列表中,不然任选
  • --requestheader-username-headers:指定HTTP Header中用于设置用户名的字段名称列表,API Server按照顺序检查用户身份,第一个匹配结果作为用户名,必选
  • --requestheader-group-headers:指定在HTTP Header中用于设置用户组名,可选
  • --requestheader-extra-headers-prefix:指定其他用户信息的Header前缀列表

RequestHeader认证实现原理

所有的Authenticator.Request接口

  • Ref:https://github.com/kubernetes/apiserver/blob/ba592e4ccd41a320ceb91bab90eebee3bb4a4f33/pkg/authentication/request/headerrequest/requestheader.go#L157

    func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
    name := headerValue(req.Header, a.nameHeaders.Value())
    if len(name) == 0 {
    return nil, false, nil
    }
    groups := allHeaderValues(req.Header, a.groupHeaders.Value())
    extra := newExtra(req.Header, a.extraHeaderPrefixes.Value())

    // clear headers used for authentication
    ClearAuthenticationHeaders(req.Header, a.nameHeaders, a.groupHeaders, a.extraHeaderPrefixes)

    return &authenticator.Response{
    User: &user.DefaultInfo{
    Name: name,
    Groups: groups,
    Extra: extra,
    },
    }, true, nil
    }

Client CA认证

Client CA认证也被成为TLS双向认证

  1. 启用Client CA认证

    kube-apiserver 通过制定--client-ca-file参数启用ClientCA认证
  2. ClientCA认证实现原理

    • Ref:https://github.com/kubernetes/apiserver/blob/ba592e4ccd41a320ceb91bab90eebee3bb4a4f33/pkg/authentication/request/x509/x509.go#L133


      // AuthenticateRequest authenticates the request using presented client certificates
      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,
      )
      }

      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)
      }
    • Ref:https://github.com/kubernetes/apiserver/blob/ba592e4ccd41a320ceb91bab90eebee3bb4a4f33/pkg/authentication/request/x509/x509.go#L248C32-L248C50

      // CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName
      var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
      if len(chain[0].Subject.CommonName) == 0 {
      return nil, false, nil
      }
      return &authenticator.Response{
      User: &user.DefaultInfo{
      Name: chain[0].Subject.CommonName,
      Groups: chain[0].Subject.Organization,
      },
      }, true, nil
      })
    • 证书的CommonName会被作为用户名,而Organization则被识别为用户组,默认情况下,kubelete利用证书实现身份认证,通过

      [root@hcss-ecs-5425 ~]# openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -text
      Certificate:
      Data:
      Version: 3 (0x2)
      Serial Number: 6091825874385188420 (0x548a835b5c433644)
      Signature Algorithm: sha256WithRSAEncryption
      Issuer: CN=kubernetes
      Validity
      Not Before: Nov 8 12:53:45 2024 GMT
      Not After : Nov 8 12:53:47 2025 GMT
      Subject: O=system:nodes, CN=system:node:node
      Subject Public Key Info:
      Public Key Algorithm: rsaEncryption
      Public-Key: (2048 bit)
      Modulus:
      00:ec:d5:47:0d:10:d0:45:6f:01:d9:3f:f2:65:14:
      d4:a2:d8:ec:99:a6:fd:85:26:21:40:e9:df:79:5a:
      51:f6:09:c6:cb:a2:63:da:04:8b:3f:a5:a4:62:0b:
      74:1c:7e:8d:d8:fd:95:dc:f2:8c:bb:b8:f6:c9:f0:
      a5:fb:25:a9:94:ab:17:62:7e:46:84:17:1e:fe:ea:
      40:5b:12:63:20:cd:f4:f8:51:ec:fb:c3:0e:9b:cc:
      7e:f8:f7:2d:dc:2d:a0:c0:64:08:99:71:82:73:f6:
      7e:b0:ed:27:39:f3:7c:4c:c0:27:f0:2e:a6:c1:fd:
      70:20:29:f5:b3:07:d8:7a:62:6d:f0:56:5c:f3:cf:
      98:59:6a:d1:7d:af:84:6f:b9:af:78:fc:b9:6f:4d:
      de:f5:36:8e:b4:23:81:e7:2f:1f:35:d8:c3:65:7b:
      02:ad:7f:e8:69:e9:d7:7e:f1:48:45:0d:7e:d4:a7:
      ec:62:30:48:b6:b2:42:b2:78:e5:20:ae:6d:b2:a3:
      ef:13:fe:76:1a:cb:be:25:b7:19:00:66:14:b7:43:
      44:0c:0d:f2:2f:74:89:d9:08:8d:c3:b1:d4:40:0c:
      0d:d8:af:39:f5:fd:25:80:bb:36:60:f8:9c:e2:cb:
      0f:4b:20:b9:2f:38:77:b8:23:e4:eb:7e:03:df:45:
      bd:2d
      Exponent: 65537 (0x10001)
      X509v3 extensions:
      X509v3 Key Usage: critical
      Digital Signature, Key Encipherment
      X509v3 Extended Key Usage:
      TLS Web Client Authentication
      X509v3 Basic Constraints: critical
      CA:FALSE
      X509v3 Authority Key Identifier:
      keyid:E2:BF:05:8C:DE:F5:BA:A4:7F:CA:51:0B:26:17:E4:E3:63:14:0F:9C

      Signature Algorithm: sha256WithRSAEncryption
      16:d6:6a:52:12:99:2b:03:55:d3:a9:59:e6:95:8c:59:83:8b:
      7f:ae:01:f0:04:78:32:00:18:05:9a:80:e1:3a:e6:bb:8a:63:
      dd:48:a0:9b:9c:90:f0:f7:8c:13:f4:79:71:28:67:74:b9:d4:
      8e:bb:ba:56:c4:db:25:4c:61:f7:6c:09:6e:a5:6b:c8:c1:b7:
      94:14:20:90:ee:6c:1f:d1:96:32:17:33:7f:c4:de:69:fc:b0:
      ac:f9:c3:ba:95:4a:da:bc:39:69:d2:02:3f:99:ed:8c:d7:ae:
      e7:f6:a3:50:fa:31:e7:89:2e:29:cf:7a:d2:c1:ea:e4:8d:9a:
      fc:cd:83:aa:22:7a:36:41:a9:27:a8:b0:5f:a1:3a:4f:02:18:
      f7:d0:b4:6e:27:63:8a:c5:ec:77:0b:b1:2d:c8:1b:a7:3d:fd:
      95:bc:3f:1a:cb:c7:d8:da:a7:30:1e:69:ba:48:c4:c6:28:f2:
      f9:26:69:fd:b8:01:8d:69:a8:1c:b2:51:ae:cd:01:09:fd:82:
      d2:6e:00:2d:d3:65:ee:c1:65:a2:93:68:d0:8d:af:39:1c:c7:
      2f:a4:7f:0a:22:c2:2c:33:d3:90:ce:1c:50:6e:85:e0:e7:72:
      10:0d:97:5f:5d:3f:a2:c0:02:6d:3f:f7:8d:88:c0:bb:7c:18:
      0a:32:c4:11
      -----BEGIN CERTIFICATE-----
      MIIDHzCCAgegAwIBAgIIVIqDW1xDNkQwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
      AxMKa3ViZXJuZXRlczAeFw0yNDExMDgxMjUzNDVaFw0yNTExMDgxMjUzNDdaMDIx
      FTATBgNVBAoTDHN5c3RlbTpub2RlczEZMBcGA1UEAxMQc3lzdGVtOm5vZGU6bm9k
      ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOzVRw0Q0EVvAdk/8mUU
      1KLY7Jmm/YUmIUDp33laUfYJxsuiY9oEiz+lpGILdBx+jdj9ldzyjLu49snwpfsl
      qZSrF2J+RoQXHv7qQFsSYyDN9PhR7PvDDpvMfvj3LdwtoMBkCJlxgnP2frDtJznz
      fEzAJ/AupsH9cCAp9bMH2HpibfBWXPPPmFlq0X2vhG+5r3j8uW9N3vU2jrQjgecv
      HzXYw2V7Aq1/6Gnp137xSEUNftSn7GIwSLayQrJ45SCubbKj7xP+dhrLviW3GQBm
      FLdDRAwN8i90idkIjcOx1EAMDdivOfX9JYC7NmD4nOLLD0sguS84d7gj5Ot+A99F
      vS0CAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMC
      MAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU4r8FjN71uqR/ylELJhfk42MUD5ww
      DQYJKoZIhvcNAQELBQADggEBABbWalISmSsDVdOpWeaVjFmDi3+uAfAEeDIAGAWa
      gOE65ruKY91IoJuckPD3jBP0eXEoZ3S51I67ulbE2yVMYfdsCW6la8jBt5QUIJDu
      bB/RljIXM3/E3mn8sKz5w7qVStq8OWnSAj+Z7YzXruf2o1D6MeeJLinPetLB6uSN
      mvzNg6oiejZBqSeosF+hOk8CGPfQtG4nY4rF7HcLsS3IG6c9/ZW8PxrLx9japzAe
      abpIxMYo8vkmaf24AY1pqByyUa7NAQn9gtJuAC3TZe7BZaKTaNCNrzkcxy+kfwoi
      wiwz05DOHFBuheDnchANl19dP6LAAm0/942IwLt8GAoyxBE=
      -----END CERTIFICATE-----
      [root@hcss-ecs-5425 ~]#

TokenAuth认证

服务端为了客户端的身份认证,AuthToken是基于Token的认证

启用TokenAuth认证

kube-apiserver通过制定--token-auth-file启用TokenAuth认证,Token Auth File是一个CSV格式的文件,每个用户在CSV中的格式为“token,user,name,user,uid,group”,Group是可选字段

TokenAuth认证实现原理

基于Token的认证器先实现authentucator.Token接口,进一步被分装为authenticator.Request接口的Authenticator和ProtocolAuthenticator分别用于普通的HTTP BearerToken 和面向Websocket连接的Bearer Token认证

ServiceAccountAuth认证

ServiceAccountAuth被称为服务账号令牌。

K8s有俩种用户:

  • Normal users普通用户
  • Service Account:服务账号

Service Account主要包含三个内容:Namespace、GA和Token

  • Namespace:指定Pod所在的命名空间
  • CA:kube-apiserver的CA公钥证书,Pod中的进程用它对kube-apiserver的提供了证书进行验证
  • Token:用于身份验证,通过kube-apiserver私钥签发经过Base64编码的Bearer Token
  • 它们都被挂载到Pod中
    • Namespace的存储路径:/var/run/secrets/kubernetes.io/serviceaccount/namespace
    • CA的存储路径:/var/run/secretskubernetes.io/serviceaccount/ca.crt
    • Token的存储路径:/var/run/secretskubernetes.io/serviceaccount/ca.crt
  1. 启用ServiceAccountAuth认证

    kube-apiserver通过以下参数启用ServiceAccountAuth认证:

    • --service-account-key-file:指定验证签名使用的公钥文件路径,如果没有指定使用kube-apiserver 的TLS密钥

    • --service-account-signing-key-file:指定使用私有文件路径,自动为颁发的Token进行签名,可选

    • --service-account-lookup:验证Service Account Token是否存在etcd,默认true

    • --service-account-isssuer:用于指定Token颁发者表示,可以是字符串或者URL地址,可以颁发多个issuser

  2. ServiceAccountAuth认证实现原理

    ServiceAccountAuth认证也是基于Token进行的

    • 旧版本的K8s,每个Service Account会分配一个secret对象,Secret永久保存有效Token

    • 从1.24开始,不再自动为ServiceAccount分配secret对象,而是通过TokenRequestAPI动态申请Token自动挂载到Pod中

    • Pod启动的时候,自动配置一个ServiceAccount(如果没有配置,使用相同命名空间的SA)

    • Ref:https://github.com/kubernetes/kubernetes/blob/88e994f6bf8fc88114c5b733e09afea339bea66d/pkg/serviceaccount/jwt.go#L262


      func (j *jwtTokenAuthenticator) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) {
      if !j.hasCorrectIssuer(tokenData) {
      return nil, false, nil
      }

      tok, err := jwt.ParseSigned(tokenData)
      if err != nil {
      return nil, false, nil
      }

      public := &jwt.Claims{}
      private := j.validator.NewPrivateClaims()

      // TODO: Pick the key that has the same key ID as `tok`, if one exists.
      var (
      found bool
      errlist []error
      )
      for _, key := range j.keys {
      if err := tok.Claims(key, public, private); err != nil {
      errlist = append(errlist, err)
      continue
      }
      found = true
      break
      }
      ...
  • 当Token通过验证,请求的用户名被设置为system:serviceaccount::,请求的组名有俩个,system:serviceaccounts和system:servicenamecounts:,UID即是ServiceAccount资源对象的UID

BootstrapToken认证

K8s提供了BootstrapToken认证(引导认证),把客户端的Token和服务端的Token进行匹配,认证通过之后,自动为节点颁发证书

客户端的请求头格式为:Authorization:Bearer xxx

  • xxx的格式为一个是TokenID.TokenSecret

  • Ref:https://github.com/kubernetes/apiserver/blob/ba592e4ccd41a320ceb91bab90eebee3bb4a4f33/pkg/authentication/request/bearertoken/bearertoken.go#L42

    func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
    auth := strings.TrimSpace(req.Header.Get("Authorization"))
    if auth == "" {
    return nil, false, nil
    }
    parts := strings.SplitN(auth, " ", 3)
    if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
    return nil, false, nil
    }

    token := parts[1]

    // Empty bearer tokens aren't valid
    if len(token) == 0 {
    // The space before the token case
    if len(parts) == 3 {
    warning.AddWarning(req.Context(), "", invalidTokenWithSpaceWarning)
    }
    return nil, false, nil
    }

    resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
    // if we authenticated successfully, go ahead and remove the bearer token so that no one
    // is ever tempted to use it inside of the API server
    if ok {
    req.Header.Del("Authorization")
    }

    // If the token authenticator didn't error, provide a default error
    if !ok && err == nil {
    err = invalidToken
    }

    return resp, ok, err
    }
  • WebSocket的连接请求也是支持Token认证

    • Ref:https://github.com/kubernetes/apiserver/blob/ba592e4ccd41a320ceb91bab90eebee3bb4a4f33/pkg/authentication/request/websocket/protocol.go#L48


      func (a *ProtocolAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
      // Only accept websocket connections
      if !wsstream.IsWebSocketRequest(req) {
      return nil, false, nil
      }

      token := ""
      sawTokenProtocol := false
      filteredProtocols := []string{}
      for _, protocolHeader := range req.Header[protocolHeader] {
      for _, protocol := range strings.Split(protocolHeader, ",") {
      protocol = strings.TrimSpace(protocol)

      if !strings.HasPrefix(protocol, bearerProtocolPrefix) {
      filteredProtocols = append(filteredProtocols, protocol)
      continue
      }

      if sawTokenProtocol {
      return nil, false, errors.New("multiple base64.bearer.authorization tokens specified")
      }
      sawTokenProtocol = true

      encodedToken := strings.TrimPrefix(protocol, bearerProtocolPrefix)
      decodedToken, err := base64.RawURLEncoding.DecodeString(encodedToken)
      if err != nil {
      return nil, false, errors.New("invalid base64.bearer.authorization token encoding")
      }
      if !utf8.Valid(decodedToken) {
      return nil, false, errors.New("invalid base64.bearer.authorization token")
      }
      token = string(decodedToken)
      }
      }

      // Must pass at least one other subprotocol so that we can remove the one containing the bearer token,
      // and there is at least one to echo back to the client
      if len(token) > 0 && len(filteredProtocols) == 0 {
      return nil, false, errors.New("missing additional subprotocol")
      }

      if len(token) == 0 {
      return nil, false, nil
      }

      resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)

      // on success, remove the protocol with the token
      if ok {
      // https://tools.ietf.org/html/rfc6455#section-11.3.4 indicates the Sec-WebSocket-Protocol header may appear multiple times
      // in a request, and is logically the same as a single Sec-WebSocket-Protocol header field that contains all values
      req.Header.Set(protocolHeader, strings.Join(filteredProtocols, ","))
      }

      // If the token authenticator didn't error, provide a default error
      if !ok && err == nil {
      err = errInvalidToken
      }

      return resp, ok, err
      }

启用BootstrapToken认证

kube-apiserver通过制定--enable-bootstrap-token-auth参数启用BootrapToken认证

BootstrapToken认证实现原理

    1. 根据parseToken解析出TokenID和TokenSecret
    1. 根据固定规则(bootstrap-token-)从K8s中获取Secret,验证Secret有效性
  • Ref:https://github.com/kubernetes/kubernetes/blob/88e994f6bf8fc88114c5b733e09afea339bea66d/plugin/pkg/auth/authenticator/token/bootstrap/bootstrap.go#L90

    // AuthenticateToken tries to match the provided token to a bootstrap token secret
    // in a given namespace. If found, it authenticates the token in the
    // "system:bootstrappers" group and with the "system:bootstrap:(token-id)" username.
    //
    // All secrets must be of type "bootstrap.kubernetes.io/token". An example secret:
    //
    // apiVersion: v1
    // kind: Secret
    // metadata:
    // # Name MUST be of form "bootstrap-token-( token id )".
    // name: bootstrap-token-( token id )
    // namespace: kube-system
    // # Only secrets of this type will be evaluated.
    // type: bootstrap.kubernetes.io/token
    // data:
    // token-secret: ( private part of token )
    // token-id: ( token id )
    // # Required key usage.
    // usage-bootstrap-authentication: true
    // auth-extra-groups: "system:bootstrappers:custom-group1,system:bootstrappers:custom-group2"
    // # May also contain an expiry.
    //
    // Tokens are expected to be of the form:
    //
    // ( token-id ).( token-secret )
    func (t *TokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
    tokenID, tokenSecret, err := bootstraptokenutil.ParseToken(token)
    if err != nil {
    // Token isn't of the correct form, ignore it.
    return nil, false, nil
    }

    secretName := bootstrapapi.BootstrapTokenSecretPrefix + tokenID
    secret, err := t.lister.Get(secretName)
    if err != nil {
    if errors.IsNotFound(err) {
    klog.V(3).Infof("No secret of name %s to match bootstrap bearer token", secretName)
    return nil, false, nil
    }
    return nil, false, err
    }

    if secret.DeletionTimestamp != nil {
    tokenErrorf(secret, "is deleted and awaiting removal")
    return nil, false, nil
    }

    if string(secret.Type) != string(bootstrapapi.SecretTypeBootstrapToken) || secret.Data == nil {
    tokenErrorf(secret, "has invalid type, expected %s.", bootstrapapi.SecretTypeBootstrapToken)
    return nil, false, nil
    }

    ts := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenSecretKey)
    if subtle.ConstantTimeCompare([]byte(ts), []byte(tokenSecret)) != 1 {
    tokenErrorf(secret, "has invalid value for key %s.", bootstrapapi.BootstrapTokenSecretKey)
    return nil, false, nil
    }

    id := bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenIDKey)
    if id != tokenID {
    tokenErrorf(secret, "has invalid value for key %s.", bootstrapapi.BootstrapTokenIDKey)
    return nil, false, nil
    }

    if bootstrapsecretutil.HasExpired(secret, time.Now()) {
    // logging done in isSecretExpired method.
    return nil, false, nil
    }

    if bootstrapsecretutil.GetData(secret, bootstrapapi.BootstrapTokenUsageAuthentication) != "true" {
    tokenErrorf(secret, "not marked %s=true.", bootstrapapi.BootstrapTokenUsageAuthentication)
    return nil, false, nil
    }

    groups, err := bootstrapsecretutil.GetGroups(secret)
    if err != nil {
    tokenErrorf(secret, "has invalid value for key %s: %v.", bootstrapapi.BootstrapTokenExtraGroupsKey, err)
    return nil, false, nil
    }

    return &authenticator.Response{
    User: &user.DefaultInfo{
    Name: bootstrapapi.BootstrapUserPrefix + string(id),
    Groups: groups,
    },
    }, true, nil
    }

OIDC认证

OIDC认证是一套基于OAtuh2.0协议的亲量级别认证规范,提供通过API进行身份交互的框架

认证流程:

  1. Client->AuthServer,得到access_token,id_token和refresh_token
  2. 客户端把id_token配置到Application中(如kubectl –token参数或者kubeconfig文件)
  3. 客户端使用Token对应的User身份访问kube-apiserver

kube-apiserver和Auth Server没有直接交互,而是通过证书验证签名的方法实现了坚定Token是否合法,下面是OIDC认证完整流程:

  1. 用户登陆提供商Identity Provider(Auth Server)
  2. Auth Server认证用户身份,返回access_token、id_token和refresh_token
  3. 用户使用kubectl,通过–token指定id_token,或者把id_token写入到kubeconfig
  4. kubectl和id_token设置为Authorization的请求头,发送到kube-apiserver
  5. kube-apiserver通过配置的证书验证JWT签名是否有效
  6. 检查确保id_token未过期
  7. 检查确保用户获取授权
  8. 获得授权后,kube-apiserver指定对应的操作,返回响应给客户端kubectl
  9. kubectl向用户提供结果反馈

kube-apiserver为了不和Auth Server交互认证Token,在第五步:

  • JWT都被其颁发AuthServer进行了数字签名,只需要kube-apiserver中配置信任的Auth,只需要在kube-apiserver中配置所认证的AuthServer证书,使用它验证收到的id_token中签名是否合法
  • 基于使用这种PKI的验证机制,在配置完成后,在认证过程kube-apiserver无需和Auth Server有任何交互

启用OIDC认证

kube-apiserver通过指定参数启用OIDC认证:

  • --oidc-issuer-url:Auth Server的URL,kube-apiserver可以通过该地址发现和读取签名验证需要的信息,必选
  • --oidc-client-id:所有的Token颁发使用的CLientID,如k8s,必选
  • --oidc-username-claim:JWT声明的用户名称(默认为sub),可选
  • --oidc-username-prefix:用户名称前缀,所有用户名称添加前缀,禁用前缀需要使用-,可选
  • --oidc-groups-claim:JWT声明组名,可选
  • --oidc-groups-prefix:组名前缀,可选
  • --oidc-ca-file:使用改CA验证OpenID Server提供的Web证书,没有设置使用操作系统的rootCA验证,可选
  • --oidc-signing-algs:可以被接受的签名算法,当前支持RS256,RS384,RS51,ES256,ES384,ES512,PS256,PS382,PS512
  • --oidc-required-claim:键值对,用于藐视IDToken中必要声明,如果设置,验证声明中是否有匹配值存在IDToken中,重复指定此参数多个声明,可选

OIDC认证实现原理

  • Ref:https://github.com/kubernetes/apiserver/blob/ba592e4ccd41a320ceb91bab90eebee3bb4a4f33/plugin/pkg/authenticator/token/oidc/oidc.go#L554

    func (a *Authenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
    if !hasCorrectIssuer(a.issuerURL, token) {
    return nil, false, nil
    }

    verifier, ok := a.idTokenVerifier()
    if !ok {
    return nil, false, fmt.Errorf("oidc: authenticator not initialized")
    }

    idToken, err := verifier.Verify(ctx, token)
    if err != nil {
    return nil, false, fmt.Errorf("oidc: verify token: %v", err)
    }

    var c claims
    if err := idToken.Claims(&c); err != nil {
    return nil, false, fmt.Errorf("oidc: parse claims: %v", err)
    }
    if a.resolver != nil {
    if err := a.resolver.expand(c); err != nil {
    return nil, false, fmt.Errorf("oidc: could not expand distributed claims: %v", err)
    }
    }

    var username string
    if err := c.unmarshalClaim(a.usernameClaim, &username); err != nil {
    return nil, false, fmt.Errorf("oidc: parse username claims %q: %v", a.usernameClaim, err)
    }

    if a.usernameClaim == "email" {
    // If the email_verified claim is present, ensure the email is valid.
    // https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
    if hasEmailVerified := c.hasClaim("email_verified"); hasEmailVerified {
    var emailVerified bool
    if err := c.unmarshalClaim("email_verified", &emailVerified); err != nil {
    return nil, false, fmt.Errorf("oidc: parse 'email_verified' claim: %v", err)
    }

    // If the email_verified claim is present we have to verify it is set to `true`.
    if !emailVerified {
    return nil, false, fmt.Errorf("oidc: email not verified")
    }
    }
    }

    if a.usernamePrefix != "" {
    username = a.usernamePrefix + username
    }

    info := &user.DefaultInfo{Name: username}
    if a.groupsClaim != "" {
    if _, ok := c[a.groupsClaim]; ok {
    // Some admins want to use string claims like "role" as the group value.
    // Allow the group claim to be a single string instead of an array.
    //
    // See: https://github.com/kubernetes/kubernetes/issues/33290
    var groups stringOrArray
    if err := c.unmarshalClaim(a.groupsClaim, &groups); err != nil {
    return nil, false, fmt.Errorf("oidc: parse groups claim %q: %v", a.groupsClaim, err)
    }
    info.Groups = []string(groups)
    }
    }

    if a.groupsPrefix != "" {
    for i, group := range info.Groups {
    info.Groups[i] = a.groupsPrefix + group
    }
    }

    // check to ensure all required claims are present in the ID token and have matching values.
    for claim, value := range a.requiredClaims {
    if !c.hasClaim(claim) {
    return nil, false, fmt.Errorf("oidc: required claim %s not present in ID token", claim)
    }

    // NOTE: Only string values are supported as valid required claim values.
    var claimValue string
    if err := c.unmarshalClaim(claim, &claimValue); err != nil {
    return nil, false, fmt.Errorf("oidc: parse claim %s: %v", claim, err)
    }
    if claimValue != value {
    return nil, false, fmt.Errorf("oidc: required claim %s value does not match. Got = %s, want = %s", claim, claimValue, value)
    }
    }

    return &authenticator.Response{User: info}, true, nil
    }

WebhookTokenAuth认证

Webhook称为钩子,基于HTTP回调的机制

启用WebhookTokenAuth认证

kube-apiserver通过制定参数启用WebhookTokenAuth认证

  • --authentication-token-webhook-config-file:kubeconfig格式的Webhook配置文件,描述了如何访问远程Webhook服务器
  • --authentication-token-webhook-cache-ttl:认证结果返回,默认2min
  • --authentication-token-webhook-cache-version:指定Webhook服务器交互使用TokenReview API版本

WebhookTokenAuth认证实现原理

Anonymous认证

Anonymous认证称为匿名认证,未被其他认证器拒绝的请求视为匿名请求

启用Anonymous认证

kube-apiserber通过制定--anonymous-auth启用Anonymous认证,默认false

Anonymous认证实现原理