身份认证

本页提供身份认证的概述。

Kubernetes 中的用户

所有 Kubernetes 集群都有两类用户:由 Kubernetes 管理的 Service Account 和普通用户。

假设由独立于集群的服务通过以下方式管理普通用户:

  • 管理员分发私钥
  • 用户存储,例如 Keystone 或 Google Accounts
  • 包含用户名和密码列表的文件

在这方面,Kubernetes 没有表示普通用户帐户的对象。普通用户不能通过 API 调用添加到集群。

尽管普通用户不能通过 API 调用添加,但任何出示由集群的证书颁发机构 (CA) 签发的有效证书的用户都被视为已通过身份认证。在这种配置中,Kubernetes 从证书的“主题”中的公共名称字段确定用户名(例如,“/CN=bob”)。在此基础上,基于角色的访问控制 (RBAC) 子系统将确定用户是否被授权对资源执行特定操作。

相比之下,Service Account 是由 Kubernetes API 管理的用户。它们绑定到特定的命名空间,并由 API 服务器自动创建或通过 API 调用手动创建。Service Account 与一组存储为 Secrets 的凭据相关联,这些凭据被挂载到 Pod 中,允许集群内的进程与 Kubernetes API 通信。

API 请求与普通用户或 Service Account 相关联,或者被视为匿名请求。这意味着集群内外所有进程,从工作站上键入 kubectl 的人类用户,到节点上的 kubelets,再到控制面成员,在向 API 服务器发出请求时都必须进行身份认证,否则将被视为匿名用户。

身份认证策略

Kubernetes 使用客户端证书、Bearer Token 或身份认证代理通过身份认证插件对 API 请求进行身份认证。当向 API 服务器发出 HTTP 请求时,插件会尝试将以下属性与请求关联起来:

  • Username:标识最终用户的字符串。常见值可能是 kube-adminjane@example.com
  • UID:标识最终用户的字符串,旨在比用户名更一致和唯一。
  • Groups:一组字符串,每个字符串表示用户属于一个命名的逻辑用户集合。常见值可能是 system:mastersdevops-team
  • Extra fields:一个字符串到字符串列表的映射,用于保存授权者可能发现有用的额外信息。

所有值对于身份认证系统来说都是不透明的,只有由授权者解释时才有意义。

你可以同时启用多种身份认证方法。通常应至少使用两种方法:

  • Service Account 的 Service Account Token
  • 至少另一种用于用户身份认证的方法。

当启用了多个身份认证模块时,第一个成功对请求进行身份认证的模块将短路评估。API 服务器不保证身份认证器运行的顺序。

system:authenticated 组包含在所有已认证用户的组列表中。

与其它身份认证协议(LDAP、SAML、Kerberos、其他 x509 方案等)的集成可以通过使用身份认证代理身份认证 Webhook 来实现。

X509 客户端证书

通过向 API 服务器传递 --client-ca-file=SOMEFILE 选项启用客户端证书身份认证。引用文件中必须包含一个或多个证书颁发机构,用于验证提供给 API 服务器的客户端证书。如果提供了客户端证书并已验证,则主题的公共名称将用作请求的用户名。从 Kubernetes 1.4 开始,客户端证书还可以使用证书的组织字段指示用户的组成员身份。要为一个用户包含多个组成员身份,请在证书中包含多个组织字段。

例如,使用 openssl 命令行工具生成证书签名请求:

openssl req -new -key jbeda.pem -out jbeda-csr.pem -subj "/CN=jbeda/O=app1/O=app2"

这将创建一个用户名为“jbeda”,属于“app1”和“app2”两个组的 CSR。

有关如何生成客户端证书,请参阅管理证书

静态 Token 文件

当在命令行中给定 --token-auth-file=SOMEFILE 选项时,API 服务器从文件中读取 Bearer Token。目前,Token 的有效期是无限期的,并且 Token 列表无法在不重新启动 API 服务器的情况下更改。

Token 文件是一个 CSV 文件,至少包含 3 列:Token、用户名、用户 UID,后跟可选的组名。

在请求中放置 Bearer Token

当使用来自 HTTP 客户端的 Bearer Token 身份认证时,API 服务器期望一个 Authorization 请求头,其值为 Bearer <token>。Bearer Token 必须是一个字符序列,可以使用 HTTP 的编码和引用设施将其放入 HTTP 请求头的值中。例如:如果 Bearer Token 是 31ada4fd-adec-460c-809a-9e56ceb75269,则它在 HTTP 请求头中会如下所示:

Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269

Bootstrap Token

特性状态: Kubernetes v1.18 [稳定]

为了简化新集群的引导过程,Kubernetes 包含一种称为 Bootstrap Token 的动态管理的 Bearer Token 类型。这些 Token 存储在 kube-system 命名空间中的 Secrets 中,可以在其中进行动态管理和创建。Controller Manager 包含一个 TokenCleaner 控制器,用于删除过期的 Bootstrap Token。

Token 的形式是 [a-z0-9]{6}.[a-z0-9]{16}。第一个组成部分是 Token ID,第二个组成部分是 Token Secret。你在 HTTP 请求头中指定 Token 的方式如下:

Authorization: Bearer 781292.db7bc3a58fc5f07e

你必须在 API 服务器上使用 --enable-bootstrap-token-auth 标志启用 Bootstrap Token 身份认证器。你必须通过 Controller Manager 上的 --controllers 标志启用 TokenCleaner 控制器。可以通过类似 --controllers=*,tokencleaner 的设置来实现。如果你使用 kubeadm 引导集群,它会为你完成此操作。

身份认证器以 system:bootstrap:<Token ID> 的身份进行身份认证。它包含在 system:bootstrappers 组中。这种命名和分组方式是有意限制的,旨在阻止用户在引导之后继续使用这些 Token。用户名和组名可以用来(并且 kubeadm 确实这样使用)制定适当的授权策略来支持集群引导。

有关 Bootstrap Token 身份认证器和控制器的深入文档,以及如何使用 kubeadm 管理这些 Token,请参阅Bootstrap Token

Service Account Token

Service Account 是一个自动启用的身份认证器,它使用签名的 Bearer Token 来验证请求。该插件有两个可选标志:

  • --service-account-key-file 包含 PEM 编码的 x509 RSA 或 ECDSA 私钥或公钥的文件,用于验证 ServiceAccount Token。指定的文件可以包含多个密钥,并且该标志可以指定多次以使用不同的文件。如果未指定,则使用 --tls-private-key-file。
  • --service-account-lookup 如果启用,从 API 中删除的 Token 将被撤销。

Service Account 通常由 API 服务器自动创建,并通过 ServiceAccount 准入控制器与集群中运行的 Pod 相关联。Bearer Token 被挂载到 Pod 的已知位置,允许集群内进程与 API 服务器通信。可以使用 PodSpecserviceAccountName 字段将帐户显式地与 Pod 相关联。

apiVersion: apps/v1 # this apiVersion is relevant as of Kubernetes 1.9
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: default
spec:
  replicas: 3
  template:
    metadata:
    # ...
    spec:
      serviceAccountName: bob-the-bot
      containers:
      - name: nginx
        image: nginx:1.14.2

Service Account Bearer Token 完全有效,可以在集群外部使用,并可用于为希望与 Kubernetes API 通信的长期运行任务创建身份。要手动创建 Service Account,请使用 kubectl create serviceaccount (NAME) 命令。这将在当前命名空间中创建一个 Service Account。

kubectl create serviceaccount jenkins
serviceaccount/jenkins created

创建关联的 Token

kubectl create token jenkins
eyJhbGciOiJSUzI1NiIsImtp...

创建的 Token 是一个签名的 JSON Web Token (JWT)。

签名的 JWT 可以用作 Bearer Token 来以给定的 Service Account 身份进行身份认证。有关如何在请求中包含 Token 的信息,请参阅上文。通常这些 Token 被挂载到 Pod 中,用于集群内访问 API 服务器,但也可以在集群外部使用。

Service Account 使用用户名 system:serviceaccount:(NAMESPACE):(SERVICEACCOUNT) 进行身份认证,并被分配到 system:serviceaccountssystem:serviceaccounts:(NAMESPACE) 组。

OpenID Connect Token

OpenID Connect 是一种 OAuth2 变体,受某些 OAuth2 提供商支持,特别是 Microsoft Entra ID、Salesforce 和 Google。该协议对 OAuth2 的主要扩展是一个随 Access Token 返回的附加字段,称为 ID Token。此 Token 是一个 JSON Web Token (JWT),包含一些熟知字段,例如用户的电子邮件,由服务器签名。

为了标识用户,身份认证器使用 OAuth2 Token 响应中的 id_token(而非 access_token)作为 Bearer Token。有关如何在请求中包含 Token 的信息,请参阅上文

sequenceDiagram participant user as 用户 participant idp as 身份提供商 participant kube as kubectl participant api as API Server user ->> idp: 1. 登录 IdP activate idp idp -->> user: 2. 提供 access_token,
id_token 和 refresh_token deactivate idp activate user user ->> kube: 3. 调用 kubectl
使用 --token (即 id_token)
或者将 token 添加到 .kube/config deactivate user activate kube kube ->> api: 4. Authorization: Bearer... deactivate kube activate api api ->> api: 5. JWT 签名有效吗? api ->> api: 6. JWT 过期了吗?(iat+exp) api ->> api: 7. 用户被授权了吗? api -->> kube: 8. 已授权:执行
操作并返回结果 deactivate api activate kube kube --x user: 9. 返回结果 deactivate kube
  1. 登录你的身份提供商

  2. 你的身份提供商将为你提供一个 access_token、一个 id_token 和一个 refresh_token

  3. 使用 kubectl 时,将你的 id_token--token 标志一起使用,或者直接将其添加到你的 kubeconfig

  4. kubectl 将你的 id_token 放在一个名为 Authorization 的请求头中发送给 API 服务器

  5. API 服务器将确保 JWT 签名有效

  6. 检查确保 id_token 未过期

    如果使用 AuthenticationConfiguration 配置了 CEL 表达式,则执行 Claim 和/或用户验证。

  7. 确保用户已获得授权

  8. 授权后,API 服务器向 kubectl 返回响应

  9. kubectl 向用户提供反馈

由于验证你身份所需的所有数据都在 id_token 中,Kubernetes 无需“打电话回家”给身份提供商。在每个请求都是无状态的模型中,这为身份认证提供了一个非常可伸缩的解决方案。但也带来了一些挑战:

  1. Kubernetes 没有触发身份认证过程的“Web 界面”。没有浏览器或界面来收集凭据,这就是为什么你需要先向你的身份提供商进行身份认证。
  2. id_token 无法被撤销,它就像一个证书,因此它的生命周期应该很短(只有几分钟),这可能会导致每隔几分钟就必须获取一个新 Token,非常令人烦恼。
  3. 要对 Kubernetes 控制面板进行身份认证,你必须使用 kubectl proxy 命令或注入 id_token 的反向代理。

配置 API 服务器

使用标志

要启用该插件,请在 API 服务器上配置以下标志:

参数描述示例必需
--oidc-issuer-url提供商的 URL,允许 API 服务器发现公共签名密钥。只接受使用 https:// 方案的 URL。这通常是提供商的发现 URL,将其路径更改为空。如果颁发者的 OIDC 发现 URL 是 https://accounts.provider.example/.well-known/openid-configuration,则该值应为 https://accounts.provider.example
--oidc-client-id所有 Token 必须为其颁发的客户端 ID。kubernetes
--oidc-username-claim用作用户名的 JWT Claim。默认为 sub,这通常是最终用户的唯一标识符。管理员可以根据其提供商选择其他 Claim,例如 emailname。然而,除 email 以外的 Claim 将以颁发者 URL 为前缀,以防止与其它插件发生命名冲突。sub
--oidc-username-prefix添加到用户名 Claim 前缀,以防止与现有名称(例如 system: 用户)冲突。例如,值 oidc: 将创建诸如 oidc:jane.doe 的用户名。如果未提供此标志并且 --oidc-username-claim 的值不是 email,则前缀默认为 ( 颁发者 URL )#,其中 ( 颁发者 URL )--oidc-issuer-url 的值。可以使用值 - 禁用所有前缀。oidc
--oidc-groups-claim用作用户组的 JWT Claim。如果存在该 Claim,它必须是一个字符串数组。groups
--oidc-groups-prefix添加到组 Claim 前缀,以防止与现有名称(例如 system: 组)冲突。例如,值 oidc: 将创建诸如 oidc:engineeringoidc:infra 的组名。oidc
--oidc-required-claim描述 ID Token 中必需 Claim 的键值对。如果设置,则验证该 Claim 是否存在于 ID Token 中并具有匹配的值。重复此标志以指定多个 Claim。claim=value
--oidc-ca-file签署你的身份提供商 Web 证书的 CA 证书路径。默认为主机的根 CA。/etc/kubernetes/ssl/kc-ca.pem
--oidc-signing-algs接受的签名算法。默认为 "RS256"。RS512
从文件读取身份认证配置
特性状态: Kubernetes v1.30 [Beta] (默认启用:true)

JWT 身份认证器是一个用于使用符合 JWT 标准的 Token 对 Kubernetes 用户进行身份认证的身份认证器。该身份认证器将尝试解析原始 ID Token,验证它是否由配置的颁发者签名。用于验证签名的公钥通过 OIDC 发现机制从颁发者的公共端点发现。

有效 JWT payload 必须包含以下最小 Claim:

{
  "iss": "https://example.com",   // must match the issuer.url
  "aud": ["my-app"],              // at least one of the entries in issuer.audiences must match the "aud" claim in presented JWTs.
  "exp": 1234567890,              // token expiration as Unix time (the number of seconds elapsed since January 1, 1970 UTC)
  "<username-claim>": "user"      // this is the username claim configured in the claimMappings.username.claim or claimMappings.username.expression
}

配置文件方法允许你配置多个 JWT 身份认证器,每个认证器都有唯一的 issuer.urlissuer.discoveryURL。配置文件甚至允许你指定CEL 表达式,以将 Claims 映射到用户属性,并验证 Claims 和用户信息。当配置文件被修改时,API 服务器也会自动重新加载身份认证器。你可以使用 apiserver_authentication_config_controller_automatic_reload_last_timestamp_seconds 指标来监控 API 服务器上次重新加载配置的时间。

你必须使用 API 服务器上的 --authentication-config 标志指定身份认证配置文件的路径。如果你想使用命令行标志而不是配置文件,它们将继续按原样工作。要访问配置多个身份认证器、为颁发者设置多个受众等新功能,请切换到使用配置文件。

对于 Kubernetes v1.33,结构化身份认证配置文件格式处于 Beta 级别,并且使用该配置的机制也处于 Beta 阶段。如果你没有专门为集群禁用 StructuredAuthenticationConfiguration 功能门控,你可以通过为 kube-apiserver 指定 --authentication-config 命令行参数来开启结构化身份认证。结构化身份认证配置文件的示例如下所示。

---
#
# CAUTION: this is an example configuration.
#          Do not use this for your own cluster!
#
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
# list of authenticators to authenticate Kubernetes users using JWT compliant tokens.
# the maximum number of allowed authenticators is 64.
jwt:
- issuer:
    # url must be unique across all authenticators.
    # url must not conflict with issuer configured in --service-account-issuer.
    url: https://example.com # Same as --oidc-issuer-url.
    # discoveryURL, if specified, overrides the URL used to fetch discovery
    # information instead of using "{url}/.well-known/openid-configuration".
    # The exact value specified is used, so "/.well-known/openid-configuration"
    # must be included in discoveryURL if needed.
    #
    # The "issuer" field in the fetched discovery information must match the "issuer.url" field
    # in the AuthenticationConfiguration and will be used to validate the "iss" claim in the presented JWT.
    # This is for scenarios where the well-known and jwks endpoints are hosted at a different
    # location than the issuer (such as locally in the cluster).
    # discoveryURL must be different from url if specified and must be unique across all authenticators.
    discoveryURL: https://discovery.example.com/.well-known/openid-configuration
    # PEM encoded CA certificates used to validate the connection when fetching
    # discovery information. If not set, the system verifier will be used.
    # Same value as the content of the file referenced by the --oidc-ca-file flag.
    certificateAuthority: <PEM encoded CA certificates>    
    # audiences is the set of acceptable audiences the JWT must be issued to.
    # At least one of the entries must match the "aud" claim in presented JWTs.
    audiences:
    - my-app # Same as --oidc-client-id.
    - my-other-app
    # this is required to be set to "MatchAny" when multiple audiences are specified.
    audienceMatchPolicy: MatchAny
  # rules applied to validate token claims to authenticate users.
  claimValidationRules:
    # Same as --oidc-required-claim key=value.
  - claim: hd
    requiredValue: example.com
    # Instead of claim and requiredValue, you can use expression to validate the claim.
    # expression is a CEL expression that evaluates to a boolean.
    # all the expressions must evaluate to true for validation to succeed.
  - expression: 'claims.hd == "example.com"'
    # Message customizes the error message seen in the API server logs when the validation fails.
    message: the hd claim must be set to example.com
  - expression: 'claims.exp - claims.nbf <= 86400'
    message: total token lifetime must not exceed 24 hours
  claimMappings:
    # username represents an option for the username attribute.
    # This is the only required attribute.
    username:
      # Same as --oidc-username-claim. Mutually exclusive with username.expression.
      claim: "sub"
      # Same as --oidc-username-prefix. Mutually exclusive with username.expression.
      # if username.claim is set, username.prefix is required.
      # Explicitly set it to "" if no prefix is desired.
      prefix: ""
      # Mutually exclusive with username.claim and username.prefix.
      # expression is a CEL expression that evaluates to a string.
      #
      # 1.  If username.expression uses 'claims.email', then 'claims.email_verified' must be used in
      #     username.expression or extra[*].valueExpression or claimValidationRules[*].expression.
      #     An example claim validation rule expression that matches the validation automatically
      #     applied when username.claim is set to 'email' is 'claims.?email_verified.orValue(true) == true'.
      #     By explicitly comparing the value to true, we let type-checking see the result will be a boolean, and
      #     to make sure a non-boolean email_verified claim will be caught at runtime.
      # 2.  If the username asserted based on username.expression is the empty string, the authentication
      #     request will fail.
      expression: 'claims.username + ":external-user"'
    # groups represents an option for the groups attribute.
    groups:
      # Same as --oidc-groups-claim. Mutually exclusive with groups.expression.
      claim: "sub"
      # Same as --oidc-groups-prefix. Mutually exclusive with groups.expression.
      # if groups.claim is set, groups.prefix is required.
      # Explicitly set it to "" if no prefix is desired.
      prefix: ""
      # Mutually exclusive with groups.claim and groups.prefix.
      # expression is a CEL expression that evaluates to a string or a list of strings.
      expression: 'claims.roles.split(",")'
    # uid represents an option for the uid attribute.
    uid:
      # Mutually exclusive with uid.expression.
      claim: 'sub'
      # Mutually exclusive with uid.claim
      # expression is a CEL expression that evaluates to a string.
      expression: 'claims.sub'
    # extra attributes to be added to the UserInfo object. Keys must be domain-prefix path and must be unique.
    extra:
      # key is a string to use as the extra attribute key.
      # key must be a domain-prefix path (e.g. example.org/foo). All characters before the first "/" must be a valid
      # subdomain as defined by RFC 1123. All characters trailing the first "/" must
      # be valid HTTP Path characters as defined by RFC 3986.
      # k8s.io, kubernetes.io and their subdomains are reserved for Kubernetes use and cannot be used.
      # key must be lowercase and unique across all extra attributes.
    - key: 'example.com/tenant'
      # valueExpression is a CEL expression that evaluates to a string or a list of strings.
      valueExpression: 'claims.tenant'
  # validation rules applied to the final user object.
  userValidationRules:
    # expression is a CEL expression that evaluates to a boolean.
    # all the expressions must evaluate to true for the user to be valid.
  - expression: "!user.username.startsWith('system:')"
    # Message customizes the error message seen in the API server logs when the validation fails.
    message: 'username cannot used reserved system: prefix'
  - expression: "user.groups.all(group, !group.startsWith('system:'))"
    message: 'groups cannot used reserved system: prefix'
  • Claim 验证规则表达式

    jwt.claimValidationRules[i].expression 表示将由 CEL 评估的表达式。CEL 表达式可以访问 Token payload 的内容,这些内容组织在 claims CEL 变量中。claims 是一个将 Claim 名称(字符串)映射到 Claim 值(任意类型)的 Map。

  • 用户验证规则表达式

    jwt.userValidationRules[i].expression 表示将由 CEL 评估的表达式。CEL 表达式可以访问 userInfo 的内容,这些内容组织在 user CEL 变量中。有关 user 模式的信息,请参考UserInfo API 文档。

  • Claim 映射表达式

    jwt.claimMappings.username.expressionjwt.claimMappings.groups.expressionjwt.claimMappings.uid.expressionjwt.claimMappings.extra[i].valueExpression 表示将由 CEL 评估的表达式。CEL 表达式可以访问 Token payload 的内容,这些内容组织在 claims CEL 变量中。claims 是一个将 Claim 名称(字符串)映射到 Claim 值(任意类型)的 Map。

    要了解更多信息,请参阅CEL 文档

    以下是使用不同 Token payload 的 AuthenticationConfiguration 示例。

    apiVersion: apiserver.config.k8s.io/v1beta1
    kind: AuthenticationConfiguration
    jwt:
    - issuer:
        url: https://example.com
        audiences:
        - my-app
      claimMappings:
        username:
          expression: 'claims.username + ":external-user"'
        groups:
          expression: 'claims.roles.split(",")'
        uid:
          expression: 'claims.sub'
        extra:
        - key: 'example.com/tenant'
          valueExpression: 'claims.tenant'
      userValidationRules:
      - expression: "!user.username.startsWith('system:')" # the expression will evaluate to true, so validation will succeed.
        message: 'username cannot used reserved system: prefix'
    
    TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6ImY3dF9tOEROWmFTQk1oWGw5QXZTWGhBUC04Y0JmZ0JVbFVpTG5oQkgxdXMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJrdWJlcm5ldGVzIiwiZXhwIjoxNzAzMjMyOTQ5LCJpYXQiOjE3MDExMDcyMzMsImlzcyI6Imh0dHBzOi8vZXhhbXBsZS5jb20iLCJqdGkiOiI3YzMzNzk0MjgwN2U3M2NhYTJjMzBjODY4YWMwY2U5MTBiY2UwMmRkY2JmZWJlOGMyM2I4YjVmMjdhZDYyODczIiwibmJmIjoxNzAxMTA3MjMzLCJyb2xlcyI6InVzZXIsYWRtaW4iLCJzdWIiOiJhdXRoIiwidGVuYW50IjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjRhIiwidXNlcm5hbWUiOiJmb28ifQ.TBWF2RkQHm4QQz85AYPcwLxSk-VLvQW-mNDHx7SEOSv9LVwcPYPuPajJpuQn9C_gKq1R94QKSQ5F6UgHMILz8OfmPKmX_00wpwwNVGeevJ79ieX2V-__W56iNR5gJ-i9nn6FYk5pwfVREB0l4HSlpTOmu80gbPWAXY5hLW0ZtcE1JTEEmefORHV2ge8e3jp1xGafNy6LdJWabYuKiw8d7Qga__HxtKB-t0kRMNzLRS7rka_SfQg0dSYektuxhLbiDkqhmRffGlQKXGVzUsuvFw7IGM5ZWnZgEMDzCI357obHeM3tRqpn5WRjtB8oM7JgnCymaJi-P3iCd88iu1xnzA
    

    其中 Token payload 是

      {
        "aud": "kubernetes",
        "exp": 1703232949,
        "iat": 1701107233,
        "iss": "https://example.com",
        "jti": "7c337942807e73caa2c30c868ac0ce910bce02ddcbfebe8c23b8b5f27ad62873",
        "nbf": 1701107233,
        "roles": "user,admin",
        "sub": "auth",
        "tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a",
        "username": "foo"
      }
    

    使用上述 AuthenticationConfiguration 的 Token 将生成以下 UserInfo 对象并成功对用户进行身份认证。

    {
        "username": "foo:external-user",
        "uid": "auth",
        "groups": [
            "user",
            "admin"
        ],
        "extra": {
            "example.com/tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a"
        }
    }
    

    apiVersion: apiserver.config.k8s.io/v1beta1
    kind: AuthenticationConfiguration
    jwt:
    - issuer:
        url: https://example.com
        audiences:
        - my-app
      claimValidationRules:
      - expression: 'claims.hd == "example.com"' # the token below does not have this claim, so validation will fail.
        message: the hd claim must be set to example.com
      claimMappings:
        username:
          expression: 'claims.username + ":external-user"'
        groups:
          expression: 'claims.roles.split(",")'
        uid:
          expression: 'claims.sub'
        extra:
        - key: 'example.com/tenant'
          valueExpression: 'claims.tenant'
      userValidationRules:
      - expression: "!user.username.startsWith('system:')" # the expression will evaluate to true, so validation will succeed.
        message: 'username cannot used reserved system: prefix'
    
    TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6ImY3dF9tOEROWmFTQk1oWGw5QXZTWGhBUC04Y0JmZ0JVbFVpTG5oQkgxdXMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJrdWJlcm5ldGVzIiwiZXhwIjoxNzAzMjMyOTQ5LCJpYXQiOjE3MDExMDcyMzMsImlzcyI6Imh0dHBzOi8vZXhhbXBsZS5jb20iLCJqdGkiOiI3YzMzNzk0MjgwN2U3M2NhYTJjMzBjODY4YWMwY2U5MTBiY2UwMmRkY2JmZWJlOGMyM2I4YjVmMjdhZDYyODczIiwibmJmIjoxNzAxMTA3MjMzLCJyb2xlcyI6InVzZXIsYWRtaW4iLCJzdWIiOiJhdXRoIiwidGVuYW50IjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjRhIiwidXNlcm5hbWUiOiJmb28ifQ.TBWF2RkQHm4QQz85AYPcwLxSk-VLvQW-mNDHx7SEOSv9LVwcPYPuPajJpuQn9C_gKq1R94QKSQ5F6UgHMILz8OfmPKmX_00wpwwNVGeevJ79ieX2V-__W56iNR5gJ-i9nn6FYk5pwfVREB0l4HSlpTOmu80gbPWAXY5hLW0ZtcE1JTEEmefORHV2ge8e3jp1xGafNy6LdJWabYuKiw8d7Qga__HxtKB-t0kRMNzLRS7rka_SfQg0dSYektuxhLbiDkqhmRffGlQKXGVzUsuvFw7IGM5ZWnZgEMDzCI357obHeM3tRqpn5WRjtB8oM7JgnCymaJi-P3iCd88iu1xnzA
    

    其中 Token payload 是

      {
        "aud": "kubernetes",
        "exp": 1703232949,
        "iat": 1701107233,
        "iss": "https://example.com",
        "jti": "7c337942807e73caa2c30c868ac0ce910bce02ddcbfebe8c23b8b5f27ad62873",
        "nbf": 1701107233,
        "roles": "user,admin",
        "sub": "auth",
        "tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a",
        "username": "foo"
      }
    

    使用上述 AuthenticationConfiguration 的 Token 将无法通过身份认证,因为 hd Claim 未设置为 example.com。API 服务器将返回 401 Unauthorized 错误。

    apiVersion: apiserver.config.k8s.io/v1beta1
    kind: AuthenticationConfiguration
    jwt:
    - issuer:
        url: https://example.com
        audiences:
        - my-app
      claimValidationRules:
      - expression: 'claims.hd == "example.com"'
        message: the hd claim must be set to example.com
      claimMappings:
        username:
          expression: '"system:" + claims.username' # this will prefix the username with "system:" and will fail user validation.
        groups:
          expression: 'claims.roles.split(",")'
        uid:
          expression: 'claims.sub'
        extra:
        - key: 'example.com/tenant'
          valueExpression: 'claims.tenant'
      userValidationRules:
      - expression: "!user.username.startsWith('system:')" # the username will be system:foo and expression will evaluate to false, so validation will fail.
        message: 'username cannot used reserved system: prefix'
    
    TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6ImY3dF9tOEROWmFTQk1oWGw5QXZTWGhBUC04Y0JmZ0JVbFVpTG5oQkgxdXMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJrdWJlcm5ldGVzIiwiZXhwIjoxNzAzMjMyOTQ5LCJoZCI6ImV4YW1wbGUuY29tIiwiaWF0IjoxNzAxMTEzMTAxLCJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwianRpIjoiYjViMDY1MjM3MmNkMjBlMzQ1YjZmZGZmY2RjMjE4MWY0YWZkNmYyNTlhYWI0YjdlMzU4ODEyMzdkMjkyMjBiYyIsIm5iZiI6MTcwMTExMzEwMSwicm9sZXMiOiJ1c2VyLGFkbWluIiwic3ViIjoiYXV0aCIsInRlbmFudCI6IjcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0YSIsInVzZXJuYW1lIjoiZm9vIn0.FgPJBYLobo9jnbHreooBlvpgEcSPWnKfX6dc0IvdlRB-F0dCcgy91oCJeK_aBk-8zH5AKUXoFTlInfLCkPivMOJqMECA1YTrMUwt_IVqwb116AqihfByUYIIqzMjvUbthtbpIeHQm2fF0HbrUqa_Q0uaYwgy8mD807h7sBcUMjNd215ff_nFIHss-9zegH8GI1d9fiBf-g6zjkR1j987EP748khpQh9IxPjMJbSgG_uH5x80YFuqgEWwq-aYJPQxXX6FatP96a2EAn7wfPpGlPRt0HcBOvq5pCnudgCgfVgiOJiLr_7robQu4T1bis0W75VPEvwWtgFcLnvcQx0JWg
    

    其中 Token payload 是

      {
        "aud": "kubernetes",
        "exp": 1703232949,
        "hd": "example.com",
        "iat": 1701113101,
        "iss": "https://example.com",
        "jti": "b5b0652372cd20e345b6fdffcdc2181f4afd6f259aab4b7e35881237d29220bc",
        "nbf": 1701113101,
        "roles": "user,admin",
        "sub": "auth",
        "tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a",
        "username": "foo"
      }
    

    使用上述 AuthenticationConfiguration 的 Token 将生成以下 UserInfo 对象:

    {
        "username": "system:foo",
        "uid": "auth",
        "groups": [
            "user",
            "admin"
        ],
        "extra": {
            "example.com/tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a"
        }
    }
    

    这将导致用户验证失败,因为用户名以 system: 开头。API 服务器将返回 401 Unauthorized 错误。

限制
  1. 分布式 Claims 不通过CEL 表达式工作。
  2. 对于对 issuer.urlissuer.discoveryURL 的调用,不支持出站选择器配置。

Kubernetes 不提供 OpenID Connect 身份提供商。你可以使用现有的公共 OpenID Connect 身份提供商(例如 Google 或其他提供商)。或者,你可以运行自己的身份提供商,例如dexKeycloak、CloudFoundry 的UAA,或 Tremolo Security 的OpenUnison

身份提供商要与 Kubernetes 一起工作,必须满足以下条件:

  1. 支持OpenID Connect 发现

    用于验证签名的公钥是通过 OIDC 发现机制从颁发者的公共端点发现的。如果你使用身份认证配置文件,身份提供商无需公开暴露发现端点。你可以在与颁发者不同的位置(例如在集群本地)托管发现端点,并在配置文件中指定 issuer.discoveryURL

  2. 在 TLS 中运行并使用非过时加密套件

  3. 拥有 CA 签名的证书(即使 CA 不是商业 CA 或自签名 CA)

关于上述要求 #3(要求使用由 CA 签名的证书)的说明。如果你部署自己的身份提供者(而不是像 Google 或 Microsoft 这样的云提供者),你的身份提供者的 Web 服务器证书必须由一个设置了 CA 标志为 TRUE 的证书签名,即使它是自签名的。这是因为 GoLang 的 TLS 客户端实现对证书验证标准非常严格。如果你没有方便的 CA,可以使用 Dex 团队的 gencert 脚本 来创建一个简单的 CA 以及签名的证书和密钥对。或者,你也可以使用 这个类似的脚本 来生成具有更长生命周期和更大密钥大小的 SHA256 证书。

参考特定系统的设置说明

使用 kubectl

选项 1 - OIDC 认证器

第一个选项是使用 kubectl 的 oidc 认证器,它将 id_token 设置为所有请求的 Bearer Token,并在令牌过期后刷新。登录你的提供者后,使用 kubectl 添加你的 id_tokenrefresh_tokenclient_idclient_secret 来配置插件。

此插件不支持刷新令牌响应中不返回 id_token 的提供者,这些提供者应使用下面的“选项 2”。

kubectl config set-credentials USER_NAME \
   --auth-provider=oidc \
   --auth-provider-arg=idp-issuer-url=( issuer url ) \
   --auth-provider-arg=client-id=( your client id ) \
   --auth-provider-arg=client-secret=( your client secret ) \
   --auth-provider-arg=refresh-token=( your refresh token ) \
   --auth-provider-arg=idp-certificate-authority=( path to your ca certificate ) \
   --auth-provider-arg=id-token=( your id_token )

例如,在对身份提供者进行身份认证后运行以下命令

kubectl config set-credentials mmosley  \
        --auth-provider=oidc  \
        --auth-provider-arg=idp-issuer-url=https://oidcidp.tremolo.lan:8443/auth/idp/OidcIdP  \
        --auth-provider-arg=client-id=kubernetes  \
        --auth-provider-arg=client-secret=1db158f6-177d-4d9c-8a8b-d36869918ec5  \
        --auth-provider-arg=refresh-token=q1bKLFOyUiosTfawzA93TzZIDzH2TNa2SMm0zEiPKTUwME6BkEo6Sql5yUWVBSWpKUGphaWpxSVAfekBOZbBhaEW+VlFUeVRGcluyVF5JT4+haZmPsluFoFu5XkpXk5BXqHega4GAXlF+ma+vmYpFcHe5eZR+slBFpZKtQA= \
        --auth-provider-arg=idp-certificate-authority=/root/ca.pem \
        --auth-provider-arg=id-token=eyJraWQiOiJDTj1vaWRjaWRwLnRyZW1vbG8ubGFuLCBPVT1EZW1vLCBPPVRybWVvbG8gU2VjdXJpdHksIEw9QXJsaW5ndG9uLCBTVD1WaXJnaW5pYSwgQz1VUy1DTj1rdWJlLWNhLTEyMDIxNDc5MjEwMzYwNzMyMTUyIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL29pZGNpZHAudHJlbW9sby5sYW46ODQ0My9hdXRoL2lkcC9PaWRjSWRQIiwiYXVkIjoia3ViZXJuZXRlcyIsImV4cCI6MTQ4MzU0OTUxMSwianRpIjoiMm96US15TXdFcHV4WDlHZUhQdy1hZyIsImlhdCI6MTQ4MzU0OTQ1MSwibmJmIjoxNDgzNTQ5MzMxLCJzdWIiOiI0YWViMzdiYS1iNjQ1LTQ4ZmQtYWIzMC0xYTAxZWU0MWUyMTgifQ.w6p4J_6qQ1HzTG9nrEOrubxIMb9K5hzcMPxc9IxPx2K4xO9l-oFiUw93daH3m5pluP6K7eOE6txBuRVfEcpJSwlelsOsW8gb8VJcnzMS9EnZpeA0tW_p-mnkFc3VcfyXuhe5R3G7aa5d8uHv70yJ9Y3-UhjiN9EhpMdfPAoEB9fYKKkJRzF7utTTIPGrSaSU6d2pcpfYKaxIwePzEkT4DfcQthoZdy9ucNvvLoi1DIC-UocFD8HLs8LYKEqSxQvOcvnThbObJ9af71EwmuE21fO5KzMW20KtAeget1gnldOosPtz1G5EwvaQ401-RPQzPGMVBld0_zMCAwZttJ4knw

这将生成以下配置

users:
- name: mmosley
  user:
    auth-provider:
      config:
        client-id: kubernetes
        client-secret: 1db158f6-177d-4d9c-8a8b-d36869918ec5
        id-token: eyJraWQiOiJDTj1vaWRjaWRwLnRyZW1vbG8ubGFuLCBPVT1EZW1vLCBPPVRybWVvbG8gU2VjdXJpdHksIEw9QXJsaW5ndG9uLCBTVD1WaXJnaW5pYSwgQz1VUy1DTj1rdWJlLWNhLTEyMDIxNDc5MjEwMzYwNzMyMTUyIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL29pZGNpZHAudHJlbW9sby5sYW46ODQ0My9hdXRoL2lkcC9PaWRjSWRQIiwiYXVkIjoia3ViZXJuZXRlcyIsImV4cCI6MTQ4MzU0OTUxMSwianRpIjoiMm96US15TXdFcHV4WDlHZUhQdy1hZyIsImlhdCI6MTQ4MzU0OTQ1MSwibmJmIjoxNDgzNTQ5MzMxLCJzdWIiOiI0YWViMzdiYS1iNjQ1LTQ4ZmQtYWIzMC0xYTAxZWU0MWUyMTgifQ.w6p4J_6qQ1HzTG9nrEOrubxIMb9K5hzcMPxc9IxPx2K4xO9l-oFiUw93daH3m5pluP6K7eOE6txBuRVfEcpJSwlelsOsW8gb8VJcnzMS9EnZpeA0tW_p-mnkFc3VcfyXuhe5R3G7aa5d8uHv70yJ9Y3-UhjiN9EhpMdfPAoEB9fYKKkJRzF7utTTIPGrSaSU6d2pcpfYKaxIwePzEkT4DfcQthoZdy9ucNvvLoi1DIC-UocFD8HLs8LYKEqSxQvOcvnThbObJ9af71EwmuE21fO5KzMW20KtAeget1gnldOosPtz1G5EwvaQ401-RPQzPGMVBld0_zMCAwZttJ4knw
        idp-certificate-authority: /root/ca.pem
        idp-issuer-url: https://oidcidp.tremolo.lan:8443/auth/idp/OidcIdP
        refresh-token: q1bKLFOyUiosTfawzA93TzZIDzH2TNa2SMm0zEiPKTUwME6BkEo6Sql5yUWVBSWpKUGphaWpxSVAfekBOZbBhaEW+VlFUeVRGcluyVF5JT4+haZmPsluFoFu5XkpXk5BXq
      name: oidc

一旦你的 id_token 过期,kubectl 将尝试使用你的 refresh_tokenclient_secret 刷新你的 id_token,并将新的 refresh_tokenid_token 值存储在你的 .kube/config 中。

选项 2 - 使用 --token 选项

kubectl 命令允许你使用 --token 选项传入令牌。将 id_token 复制并粘贴到此选项中。

kubectl --token=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL21sYi50cmVtb2xvLmxhbjo4MDQzL2F1dGgvaWRwL29pZGMiLCJhdWQiOiJrdWJlcm5ldGVzIiwiZXhwIjoxNDc0NTk2NjY5LCJqdGkiOiI2RDUzNXoxUEpFNjJOR3QxaWVyYm9RIiwiaWF0IjoxNDc0NTk2MzY5LCJuYmYiOjE0NzQ1OTYyNDksInN1YiI6Im13aW5kdSIsInVzZXJfcm9sZSI6WyJ1c2VycyIsIm5ldy1uYW1lc3BhY2Utdmlld2VyIl0sImVtYWlsIjoibXdpbmR1QG5vbW9yZWplZGkuY29tIn0.f2As579n9VNoaKzoF-dOQGmXkFKf1FMyNV0-va_B63jn-_n9LGSCca_6IVMP8pO-Zb4KvRqGyTP0r3HkHxYy5c81AnIh8ijarruczl-TK_yF5akjSTHFZD-0gRzlevBDiH8Q79NAr-ky0P4iIXS8lY9Vnjch5MF74Zx0c3alKJHJUnnpjIACByfF2SCaYzbWFMUNat-K1PaUk5-ujMBG7yYnr95xD-63n8CO8teGUAAEMx6zRjzfhnhbzX-ajwZLGwGUBT4WqjMs70-6a7_8gZmLZb2az1cZynkFRj2BaCkVT3A2RrjeEwZEtGXlMqKJ1_I2ulrOVsYx01_yD35-rw get nodes

Webhook 令牌认证

Webhook 认证是一种用于验证 Bearer Token 的钩子。

  • --authentication-token-webhook-config-file 描述如何访问远程 webhook 服务的配置文件。
  • --authentication-token-webhook-cache-ttl 缓存认证决策的时长。默认为两分钟。
  • --authentication-token-webhook-version 决定是使用 authentication.k8s.io/v1beta1 还是 authentication.k8s.io/v1 TokenReview 对象来发送/接收来自 webhook 的信息。默认为 v1beta1

配置文件使用 kubeconfig 文件格式。文件中,clusters 指的是远程服务,而 users 指的是 API 服务器 webhook。一个例子是

# Kubernetes API version
apiVersion: v1
# kind of the API object
kind: Config
# clusters refers to the remote service.
clusters:
  - name: name-of-remote-authn-service
    cluster:
      certificate-authority: /path/to/ca.pem         # CA for verifying the remote service.
      server: https://authn.example.com/authenticate # URL of remote service to query. 'https' recommended for production.

# users refers to the API server's webhook configuration.
users:
  - name: name-of-api-server
    user:
      client-certificate: /path/to/cert.pem # cert for the webhook plugin to use
      client-key: /path/to/key.pem          # key matching the cert

# kubeconfig files require a context. Provide one for the API server.
current-context: webhook
contexts:
- context:
    cluster: name-of-remote-authn-service
    user: name-of-api-server
  name: webhook

当客户端尝试使用 如上所述的 Bearer Token 对 API 服务器进行认证时,认证 webhook 会将包含该令牌的 JSON 序列化 TokenReview 对象 POST 到远程服务。

请注意,webhook API 对象受与 其他 Kubernetes API 对象 相同的版本兼容性规则约束。实现者应检查请求的 apiVersion 字段以确保正确反序列化,并且必须返回与请求相同版本的 TokenReview 对象。

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "TokenReview",
  "spec": {
    # Opaque bearer token sent to the API server
    "token": "014fbff9a07c...",

    # Optional list of the audience identifiers for the server the token was presented to.
    # Audience-aware token authenticators (for example, OIDC token authenticators)
    # should verify the token was intended for at least one of the audiences in this list,
    # and return the intersection of this list and the valid audiences for the token in the response status.
    # This ensures the token is valid to authenticate to the server it was presented to.
    # If no audiences are provided, the token should be validated to authenticate to the Kubernetes API server.
    "audiences": ["https://myserver.example.com", "https://myserver.internal.example.com"]
  }
}

{
  "apiVersion": "authentication.k8s.io/v1beta1",
  "kind": "TokenReview",
  "spec": {
    # Opaque bearer token sent to the API server
    "token": "014fbff9a07c...",

    # Optional list of the audience identifiers for the server the token was presented to.
    # Audience-aware token authenticators (for example, OIDC token authenticators)
    # should verify the token was intended for at least one of the audiences in this list,
    # and return the intersection of this list and the valid audiences for the token in the response status.
    # This ensures the token is valid to authenticate to the server it was presented to.
    # If no audiences are provided, the token should be validated to authenticate to the Kubernetes API server.
    "audiences": ["https://myserver.example.com", "https://myserver.internal.example.com"]
  }
}

远程服务应填充请求的 status 字段来指示登录是否成功。响应体的 spec 字段将被忽略,可以省略。远程服务必须使用其收到的相同的 TokenReview API 版本返回响应。一个成功的 Bearer Token 验证将返回

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "TokenReview",
  "status": {
    "authenticated": true,
    "user": {
      # Required
      "username": "janedoe@example.com",
      # Optional
      "uid": "42",
      # Optional group memberships
      "groups": ["developers", "qa"],
      # Optional additional information provided by the authenticator.
      # This should not contain confidential data, as it can be recorded in logs
      # or API objects, and is made available to admission webhooks.
      "extra": {
        "extrafield1": [
          "extravalue1",
          "extravalue2"
        ]
      }
    },
    # Optional list audience-aware token authenticators can return,
    # containing the audiences from the `spec.audiences` list for which the provided token was valid.
    # If this is omitted, the token is considered to be valid to authenticate to the Kubernetes API server.
    "audiences": ["https://myserver.example.com"]
  }
}

{
  "apiVersion": "authentication.k8s.io/v1beta1",
  "kind": "TokenReview",
  "status": {
    "authenticated": true,
    "user": {
      # Required
      "username": "janedoe@example.com",
      # Optional
      "uid": "42",
      # Optional group memberships
      "groups": ["developers", "qa"],
      # Optional additional information provided by the authenticator.
      # This should not contain confidential data, as it can be recorded in logs
      # or API objects, and is made available to admission webhooks.
      "extra": {
        "extrafield1": [
          "extravalue1",
          "extravalue2"
        ]
      }
    },
    # Optional list audience-aware token authenticators can return,
    # containing the audiences from the `spec.audiences` list for which the provided token was valid.
    # If this is omitted, the token is considered to be valid to authenticate to the Kubernetes API server.
    "audiences": ["https://myserver.example.com"]
  }
}

不成功的请求将返回

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "TokenReview",
  "status": {
    "authenticated": false,
    # Optionally include details about why authentication failed.
    # If no error is provided, the API will return a generic Unauthorized message.
    # The error field is ignored when authenticated=true.
    "error": "Credentials are expired"
  }
}

{
  "apiVersion": "authentication.k8s.io/v1beta1",
  "kind": "TokenReview",
  "status": {
    "authenticated": false,
    # Optionally include details about why authentication failed.
    # If no error is provided, the API will return a generic Unauthorized message.
    # The error field is ignored when authenticated=true.
    "error": "Credentials are expired"
  }
}

认证代理

API 服务器可以配置为从请求头的值(如 X-Remote-User)识别用户。它设计用于与认证代理结合使用,该代理设置请求头的值。

  • --requestheader-username-headers 必填,不区分大小写。按顺序检查的用户身份的头部名称。第一个包含值的头部将用作用户名。
  • --requestheader-group-headers 1.6+。可选,不区分大小写。建议使用 "X-Remote-Group"。按顺序检查的用户组的头部名称。所有指定头部中的所有值都将用作组名。
  • --requestheader-extra-headers-prefix 1.6+。可选,不区分大小写。建议使用 "X-Remote-Extra-"。用于确定用户额外信息(通常由配置的授权插件使用)的头部前缀。以任何指定前缀开头的任何头部都将移除前缀。头部名称的其余部分将转换为小写并进行 百分比解码,成为额外信息的键;头部值则成为额外信息的值。

例如,使用此配置

--requestheader-username-headers=X-Remote-User
--requestheader-group-headers=X-Remote-Group
--requestheader-extra-headers-prefix=X-Remote-Extra-

此请求

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

将产生此用户信息

name: fido
groups:
- dogs
- dachshunds
extra:
  acme.com/project:
  - some-project
  scopes:
  - openid
  - profile

为了防止头部欺骗,在检查请求头之前,认证代理必须向 API 服务器提供有效的客户端证书,以便根据指定的 CA 进行验证。警告:除非你理解其中的风险以及保护 CA 使用的机制,否则不要在不同的上下文复用同一个 CA。

  • --requestheader-client-ca-file 必填。PEM 编码的证书捆绑。在检查请求头中的用户名之前,必须提供有效的客户端证书并根据指定文件中的证书颁发机构进行验证。
  • --requestheader-allowed-names 可选。通用名称值 (CNs) 列表。如果设置,在检查请求头中的用户名之前,必须提供具有指定列表中 CN 的有效客户端证书。如果为空,则允许任何 CN。

匿名请求

启用后,未被其他配置的认证方法拒绝的请求将被视为匿名请求,并获得 system:anonymous 的用户名和 system:unauthenticated 的组。

例如,在一个配置了令牌认证并启用了匿名访问的服务器上,提供无效 Bearer Token 的请求将收到 401 Unauthorized 错误。未提供 Bearer Token 的请求将被视为匿名请求。

在 1.5.1-1.5.x 中,匿名访问默认是禁用的,可以通过向 API 服务器传递 --anonymous-auth=true 选项来启用。

在 1.6+ 中,如果使用了 AlwaysAllow 以外的授权模式,匿名访问默认是启用的;可以通过向 API 服务器传递 --anonymous-auth=false 选项来禁用。从 1.6 开始,ABAC 和 RBAC 授权器要求对 system:anonymous 用户或 system:unauthenticated 组进行显式授权,因此授予访问 * 用户或 * 组的旧版策略规则不包括匿名用户。

匿名认证器配置

特征状态: Kubernetes v1.32 [beta] (默认启用:true)

AuthenticationConfiguration 可用于配置匿名认证器。如果你在 AuthenticationConfiguration 文件中设置了匿名字段,则不能设置 --anonymous-auth 标志。

使用认证配置文件配置匿名认证器的主要优点是,除了启用和禁用匿名认证外,你还可以配置哪些端点支持匿名认证。

下面是一个认证配置示例文件

---
#
# CAUTION: this is an example configuration.
#          Do not use this for your own cluster!
#
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
anonymous:
  enabled: true
  conditions:
  - path: /livez
  - path: /readyz
  - path: /healthz

在上述配置中,只有 /livez/readyz/healthz 端点可以通过匿名请求访问。即使 RBAC 配置允许,其他任何端点都无法访问。

用户模拟 (Impersonation)

用户可以通过模拟头文件来扮演另一个用户。这些头文件允许请求手动覆盖请求认证时的用户信息。例如,管理员可以使用此功能通过临时模拟另一个用户来调试授权策略,并查看请求是否被拒绝。

模拟请求首先以请求用户的身份进行认证,然后切换到被模拟的用户信息。

  • 用户使用他们的凭据以及模拟头文件进行 API 调用。
  • API 服务器对用户进行认证。
  • API 服务器确保认证后的用户具有模拟权限。
  • 请求用户信息被模拟值替换。
  • 请求被评估,授权对被模拟的用户信息起作用。

以下 HTTP 头文件可用于执行模拟请求

  • Impersonate-User:要扮演的用户名。
  • Impersonate-Group:要扮演的组名。可以多次提供以设置多个组。可选。需要 "Impersonate-User"。
  • Impersonate-Extra-( extra name ):一个动态头部,用于将额外字段与用户关联。可选。需要 "Impersonate-User"。为了始终保持一致,( extra name ) 必须为小写,且任何 在 HTTP 头部标签中不合法 的字符必须为 UTF8 并进行 百分比解码
  • Impersonate-Uid:表示被模拟用户的唯一标识符。可选。需要 "Impersonate-User"。Kubernetes 对此字符串没有施加任何格式要求。

模拟具有组的用户时使用的模拟头文件示例

Impersonate-User: jane.doe@example.com
Impersonate-Group: developers
Impersonate-Group: admins

模拟具有 UID 和额外字段的用户时使用的模拟头文件示例

Impersonate-User: jane.doe@example.com
Impersonate-Extra-dn: cn=jane,ou=engineers,dc=example,dc=com
Impersonate-Extra-acme.com%2Fproject: some-project
Impersonate-Extra-scopes: view
Impersonate-Extra-scopes: development
Impersonate-Uid: 06f6ce97-e2c5-4ab8-7ba5-7654dd08d52b

使用 kubectl 时,设置 --as 标志以配置 Impersonate-User 头部,设置 --as-group 标志以配置 Impersonate-Group 头部。

kubectl drain mynode
Error from server (Forbidden): User "clark" cannot get nodes at the cluster scope. (get nodes mynode)

设置 --as--as-group 标志

kubectl drain mynode --as=superman --as-group=system:masters
node/mynode cordoned
node/mynode drained

要模拟用户、组、用户标识符(UID)或额外字段,模拟用户必须能够对被模拟属性的种类("user"、"group"、"uid" 等)执行“模拟”动词。对于启用了 RBAC 授权插件的集群,以下 ClusterRole 包含了设置用户和组模拟头文件所需的规则

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: impersonator
rules:
- apiGroups: [""]
  resources: ["users", "groups", "serviceaccounts"]
  verbs: ["impersonate"]

对于模拟,额外字段和被模拟的 UID 都属于 "authentication.k8s.io" apiGroup。额外字段被评估为资源 "userextras" 的子资源。为了允许用户使用模拟头文件来模拟额外字段 "scopes" 和 UID,应授予用户以下角色

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: scopes-and-uid-impersonator
rules:
# Can set "Impersonate-Extra-scopes" header and the "Impersonate-Uid" header.
- apiGroups: ["authentication.k8s.io"]
  resources: ["userextras/scopes", "uids"]
  verbs: ["impersonate"]

模拟头部的值也可以通过限制资源可取的 resourceNames 集来限制。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: limited-impersonator
rules:
# Can impersonate the user "jane.doe@example.com"
- apiGroups: [""]
  resources: ["users"]
  verbs: ["impersonate"]
  resourceNames: ["jane.doe@example.com"]

# Can impersonate the groups "developers" and "admins"
- apiGroups: [""]
  resources: ["groups"]
  verbs: ["impersonate"]
  resourceNames: ["developers","admins"]

# Can impersonate the extras field "scopes" with the values "view" and "development"
- apiGroups: ["authentication.k8s.io"]
  resources: ["userextras/scopes"]
  verbs: ["impersonate"]
  resourceNames: ["view", "development"]

# Can impersonate the uid "06f6ce97-e2c5-4ab8-7ba5-7654dd08d52b"
- apiGroups: ["authentication.k8s.io"]
  resources: ["uids"]
  verbs: ["impersonate"]
  resourceNames: ["06f6ce97-e2c5-4ab8-7ba5-7654dd08d52b"]

client-go 凭据插件

特征状态: Kubernetes v1.22 [stable]

k8s.io/client-go 和使用它的工具(如 kubectlkubelet)能够执行外部命令来接收用户凭据。

此功能旨在用于客户端与 k8s.io/client-go 原生不支持的认证协议(LDAP、Kerberos、OAuth2、SAML 等)集成。插件实现特定协议的逻辑,然后返回不透明的凭据供使用。几乎所有凭据插件用例都需要一个支持 webhook 令牌认证器 的服务器端组件来解释客户端插件生成的凭据格式。

用例示例

在一个假设的用例中,一个组织将运行一个外部服务,用于将 LDAP 凭据交换为用户特定的签名令牌。该服务还能够响应 webhook 令牌认证器 请求来验证令牌。用户需要在其工作站上安装一个凭据插件。

对 API 进行认证

  • 用户发出 kubectl 命令。
  • 凭据插件提示用户输入 LDAP 凭据,与外部服务交换凭据以获取令牌。
  • 凭据插件将令牌返回给 client-go,client-go 将其用作对 API 服务器的 Bearer Token。
  • API 服务器使用 webhook 令牌认证器 向外部服务提交 TokenReview
  • 外部服务验证令牌上的签名并返回用户的用户名和组。

配置

通过 kubectl 配置文件 配置凭据插件,作为用户字段的一部分。

apiVersion: v1
kind: Config
users:
- name: my-user
  user:
    exec:
      # Command to execute. Required.
      command: "example-client-go-exec-plugin"

      # API version to use when decoding the ExecCredentials resource. Required.
      #
      # The API version returned by the plugin MUST match the version listed here.
      #
      # To integrate with tools that support multiple versions (such as client.authentication.k8s.io/v1beta1),
      # set an environment variable, pass an argument to the tool that indicates which version the exec plugin expects,
      # or read the version from the ExecCredential object in the KUBERNETES_EXEC_INFO environment variable.
      apiVersion: "client.authentication.k8s.io/v1"

      # Environment variables to set when executing the plugin. Optional.
      env:
      - name: "FOO"
        value: "bar"

      # Arguments to pass when executing the plugin. Optional.
      args:
      - "arg1"
      - "arg2"

      # Text shown to the user when the executable doesn't seem to be present. Optional.
      installHint: |
        example-client-go-exec-plugin is required to authenticate
        to the current cluster.  It can be installed:

        On macOS: brew install example-client-go-exec-plugin

        On Ubuntu: apt-get install example-client-go-exec-plugin

        On Fedora: dnf install example-client-go-exec-plugin

        ...        

      # Whether or not to provide cluster information, which could potentially contain
      # very large CA data, to this exec plugin as a part of the KUBERNETES_EXEC_INFO
      # environment variable.
      provideClusterInfo: true

      # The contract between the exec plugin and the standard input I/O stream. If the
      # contract cannot be satisfied, this plugin will not be run and an error will be
      # returned. Valid values are "Never" (this exec plugin never uses standard input),
      # "IfAvailable" (this exec plugin wants to use standard input if it is available),
      # or "Always" (this exec plugin requires standard input to function). Required.
      interactiveMode: Never
clusters:
- name: my-cluster
  cluster:
    server: "https://172.17.4.100:6443"
    certificate-authority: "/etc/kubernetes/ca.pem"
    extensions:
    - name: client.authentication.k8s.io/exec # reserved extension name for per cluster exec config
      extension:
        arbitrary: config
        this: can be provided via the KUBERNETES_EXEC_INFO environment variable upon setting provideClusterInfo
        you: ["can", "put", "anything", "here"]
contexts:
- name: my-cluster
  context:
    cluster: my-cluster
    user: my-user
current-context: my-cluster

apiVersion: v1
kind: Config
users:
- name: my-user
  user:
    exec:
      # Command to execute. Required.
      command: "example-client-go-exec-plugin"

      # API version to use when decoding the ExecCredentials resource. Required.
      #
      # The API version returned by the plugin MUST match the version listed here.
      #
      # To integrate with tools that support multiple versions (such as client.authentication.k8s.io/v1),
      # set an environment variable, pass an argument to the tool that indicates which version the exec plugin expects,
      # or read the version from the ExecCredential object in the KUBERNETES_EXEC_INFO environment variable.
      apiVersion: "client.authentication.k8s.io/v1beta1"

      # Environment variables to set when executing the plugin. Optional.
      env:
      - name: "FOO"
        value: "bar"

      # Arguments to pass when executing the plugin. Optional.
      args:
      - "arg1"
      - "arg2"

      # Text shown to the user when the executable doesn't seem to be present. Optional.
      installHint: |
        example-client-go-exec-plugin is required to authenticate
        to the current cluster.  It can be installed:

        On macOS: brew install example-client-go-exec-plugin

        On Ubuntu: apt-get install example-client-go-exec-plugin

        On Fedora: dnf install example-client-go-exec-plugin

        ...        

      # Whether or not to provide cluster information, which could potentially contain
      # very large CA data, to this exec plugin as a part of the KUBERNETES_EXEC_INFO
      # environment variable.
      provideClusterInfo: true

      # The contract between the exec plugin and the standard input I/O stream. If the
      # contract cannot be satisfied, this plugin will not be run and an error will be
      # returned. Valid values are "Never" (this exec plugin never uses standard input),
      # "IfAvailable" (this exec plugin wants to use standard input if it is available),
      # or "Always" (this exec plugin requires standard input to function). Optional.
      # Defaults to "IfAvailable".
      interactiveMode: Never
clusters:
- name: my-cluster
  cluster:
    server: "https://172.17.4.100:6443"
    certificate-authority: "/etc/kubernetes/ca.pem"
    extensions:
    - name: client.authentication.k8s.io/exec # reserved extension name for per cluster exec config
      extension:
        arbitrary: config
        this: can be provided via the KUBERNETES_EXEC_INFO environment variable upon setting provideClusterInfo
        you: ["can", "put", "anything", "here"]
contexts:
- name: my-cluster
  context:
    cluster: my-cluster
    user: my-user
current-context: my-cluster

相对命令路径被解释为相对于配置文件的目录。如果 KUBECONFIG 设置为 /home/jane/kubeconfig,并且 exec 命令是 ./bin/example-client-go-exec-plugin,则执行的二进制文件是 /home/jane/bin/example-client-go-exec-plugin

- name: my-user
  user:
    exec:
      # Path relative to the directory of the kubeconfig
      command: "./bin/example-client-go-exec-plugin"
      apiVersion: "client.authentication.k8s.io/v1"
      interactiveMode: Never

输入和输出格式

执行的命令将 ExecCredential 对象打印到 stdoutk8s.io/client-go 使用返回的 status 中的凭据对 Kubernetes API 进行认证。执行的命令通过 KUBERNETES_EXEC_INFO 环境变量接收一个 ExecCredential 对象作为输入。此输入包含有用的信息,例如返回的 ExecCredential 对象预期的 API 版本,以及插件是否可以使用 stdin 与用户交互。

在交互式会话(即终端)中运行时,stdin 可以直接暴露给插件。插件应使用来自 KUBERNETES_EXEC_INFO 环境变量的输入 ExecCredential 对象的 spec.interactive 字段来确定是否提供了 stdin。插件的 stdin 要求(即 stdin 是可选的、严格必需的,还是从未用于插件成功运行)通过 kubeconfig 中的 user.exec.interactiveMode 字段声明(有效值见下表)。user.exec.interactiveMode 字段在 client.authentication.k8s.io/v1beta1 中是可选的,在 client.authentication.k8s.io/v1 中是必需的。

interactiveMode 值
interactiveMode含义
从不 (Never)此 exec 插件从不需要使用标准输入,因此无论标准输入是否可用于用户输入,exec 插件都将运行。
如果可用 (IfAvailable)此 exec 插件希望在标准输入可用时使用它,但即使标准输入不可用,它仍然可以操作。因此,无论标准输入是否可用于用户输入,exec 插件都将运行。如果标准输入可用于用户输入,则将提供给此 exec 插件。
总是 (Always)此 exec 插件需要标准输入才能运行,因此只有在标准输入可用于用户输入时,exec 插件才会运行。如果标准输入不可用于用户输入,则 exec 插件将不运行,并且 exec 插件运行器将返回错误。

要使用 Bearer Token 凭据,插件在 ExecCredential 的 status 中返回一个令牌

{
  "apiVersion": "client.authentication.k8s.io/v1",
  "kind": "ExecCredential",
  "status": {
    "token": "my-bearer-token"
  }
}

{
  "apiVersion": "client.authentication.k8s.io/v1beta1",
  "kind": "ExecCredential",
  "status": {
    "token": "my-bearer-token"
  }
}

或者,可以返回 PEM 编码的客户端证书和密钥,以使用 TLS 客户端认证。如果插件在后续调用中返回不同的证书和密钥,k8s.io/client-go 将关闭与服务器的现有连接,强制进行新的 TLS 握手。

如果指定了 clientKeyDataclientCertificateData,则两者必须同时存在。

clientCertificateData 可能包含要发送到服务器的其他中间证书。

{
  "apiVersion": "client.authentication.k8s.io/v1",
  "kind": "ExecCredential",
  "status": {
    "clientCertificateData": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
    "clientKeyData": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
  }
}

{
  "apiVersion": "client.authentication.k8s.io/v1beta1",
  "kind": "ExecCredential",
  "status": {
    "clientCertificateData": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
    "clientKeyData": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
  }
}

可选地,响应可以包含凭据的过期时间,格式为 RFC 3339 时间戳。

是否存在过期时间具有以下影响

  • 如果包含过期时间,Bearer Token 和 TLS 凭据将缓存到过期时间到达,或者服务器响应 401 HTTP 状态码,或者进程退出。
  • 如果省略过期时间,Bearer Token 和 TLS 凭据将缓存到服务器响应 401 HTTP 状态码或进程退出。

{
  "apiVersion": "client.authentication.k8s.io/v1",
  "kind": "ExecCredential",
  "status": {
    "token": "my-bearer-token",
    "expirationTimestamp": "2018-03-05T17:30:20-08:00"
  }
}

{
  "apiVersion": "client.authentication.k8s.io/v1beta1",
  "kind": "ExecCredential",
  "status": {
    "token": "my-bearer-token",
    "expirationTimestamp": "2018-03-05T17:30:20-08:00"
  }
}

要使 exec 插件能够获取集群特定信息,请在 kubeconfiguser.exec 字段中设置 provideClusterInfo。然后,插件将在 KUBERNETES_EXEC_INFO 环境变量中获得此集群特定信息。此环境变量中的信息可用于执行集群特定的凭据获取逻辑。以下 ExecCredential 清单描述了一个集群信息示例。

{
  "apiVersion": "client.authentication.k8s.io/v1",
  "kind": "ExecCredential",
  "spec": {
    "cluster": {
      "server": "https://172.17.4.100:6443",
      "certificate-authority-data": "LS0t...",
      "config": {
        "arbitrary": "config",
        "this": "can be provided via the KUBERNETES_EXEC_INFO environment variable upon setting provideClusterInfo",
        "you": ["can", "put", "anything", "here"]
      }
    },
    "interactive": true
  }
}

{
  "apiVersion": "client.authentication.k8s.io/v1beta1",
  "kind": "ExecCredential",
  "spec": {
    "cluster": {
      "server": "https://172.17.4.100:6443",
      "certificate-authority-data": "LS0t...",
      "config": {
        "arbitrary": "config",
        "this": "can be provided via the KUBERNETES_EXEC_INFO environment variable upon setting provideClusterInfo",
        "you": ["can", "put", "anything", "here"]
      }
    },
    "interactive": true
  }
}

客户端对认证信息的 API 访问

特征状态: Kubernetes v1.28 [stable]

如果你的集群启用了此 API,可以使用 SelfSubjectReview API 来了解你的 Kubernetes 集群如何将你的认证信息映射到识别你作为客户端。这适用于你作为用户(通常代表真实人员)或 ServiceAccount 进行认证的情况。

SelfSubjectReview 对象没有任何可配置的字段。收到请求后,Kubernetes API 服务器会填充状态字段中的用户属性并将其返回给用户。

请求示例(主体将是一个 SelfSubjectReview

POST /apis/authentication.k8s.io/v1/selfsubjectreviews
{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "SelfSubjectReview"
}

响应示例

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "SelfSubjectReview",
  "status": {
    "userInfo": {
      "name": "jane.doe",
      "uid": "b6c7cfd4-f166-11ec-8ea0-0242ac120002",
      "groups": [
        "viewers",
        "editors",
        "system:authenticated"
      ],
      "extra": {
        "provider_id": ["token.company.example"]
      }
    }
  }
}

为了方便起见,提供了 kubectl auth whoami 命令。执行此命令将生成以下输出(尽管将显示不同的用户属性)

  • 简单输出示例

    ATTRIBUTE         VALUE
    Username          jane.doe
    Groups            [system:authenticated]
    
  • 包含额外属性的复杂示例

    ATTRIBUTE         VALUE
    Username          jane.doe
    UID               b79dbf30-0c6a-11ed-861d-0242ac120002
    Groups            [students teachers system:authenticated]
    Extra: skills     [reading learning]
    Extra: subjects   [math sports]
    

通过提供输出标志,还可以打印结果的 JSON 或 YAML 表示

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "SelfSubjectReview",
  "status": {
    "userInfo": {
      "username": "jane.doe",
      "uid": "b79dbf30-0c6a-11ed-861d-0242ac120002",
      "groups": [
        "students",
        "teachers",
        "system:authenticated"
      ],
      "extra": {
        "skills": [
          "reading",
          "learning"
        ],
        "subjects": [
          "math",
          "sports"
        ]
      }
    }
  }
}

apiVersion: authentication.k8s.io/v1
kind: SelfSubjectReview
status:
  userInfo:
    username: jane.doe
    uid: b79dbf30-0c6a-11ed-861d-0242ac120002
    groups:
    - students
    - teachers
    - system:authenticated
    extra:
      skills:
      - reading
      - learning
      subjects:
      - math
      - sports

当在 Kubernetes 集群中使用复杂的认证流程时,此功能非常有用,例如,如果你使用 webhook 令牌认证认证代理

默认情况下,当启用 APISelfSubjectReview 特征时,所有认证的用户都可以创建 SelfSubjectReview 对象。这由 system:basic-user 集群角色允许。

下一步