首页 > 其他分享 >破解 Kubernetes 身份验证 (AuthN)模型

破解 Kubernetes 身份验证 (AuthN)模型

时间:2025-01-10 20:05:18浏览次数:3  
标签:令牌 Kubernetes 用户 身份验证 apiserver token AuthN

                                      大家读完觉得有帮助和意义记得关注和点赞!!!

这篇文章深入探讨了 Kubernetes 身份验证 (AuthN) 模型。具体说来 我们将从分析 Kubernetes 中 AuthN 的技术需求开始,然后设计一个 对于它(假设它还没有),最终解决方案有一个端到端的工作流程,如下所示:

希望读者在看完这篇文章后,对 Kubernetes AuthN 模块的工作原理有更深入的了解(带策略 如静态令牌、持有者令牌、X509 证书、ServiceAccounts、OIDC 等),以及 如何以管理员身份正确配置、使用和管理它 开发 人员。


​​​​​​​

目录

1 引言

1.1 Kubernetes API 并查看会发生什么curl

1.2 AuthN 和 AuthZ

1.3 AuthN 模块的要求

1.4 本文目的

2 解决方案设计

2.1 AuthN 链

2.2 区分两种用户

2.2.1 简介ServiceAccount

2.2.2 引入 normalUser

2.3 促进 AuthZ

2.4 假设的 AuthN 工作流

2.5 支持特定的 AuthN 策略

2.6.1 支持静态令牌(适用于集群外用户/应用程序)

2.6.2 Support X509 certificate (for out-of-cluster users/apps)

2.6.3 支持(主要针对集群内应用程序)ServiceAccount

2.6.4 支持匿名用户

2.6.5 连锁

3 实施

3.1 AuthN 选项注册kube-apiserver

3.2 Kubernetes 认证链

3.3 对请求进行身份验证

3.3.1 静态令牌认证

3.3.2 X509 身份验证器

3.4 和ServiceAccountSecret

4 使用 Kubernetes AuthN 进行测试

4.1 准备工作

4.1 静态令牌

4.1.1 准备 Token 文件

4.1.2 配置kube-apiserver

4.1.3 命令行示例curl

验证 AuthN 正常(但 AuthZ 将失败)

添加并传递 AuthZRoleRoleBinding

4.1.4 使用 golang 编程示例

4.2 X509 证书

4.2.1 准备根 CA

4.2.2 配置kube-apiserver

4.2.3 为用户颁发 X509 客户端证书

4.2.4 命令行示例curl

4.2.5 使用 golang 编程示例

4.3 服务账户

4.4 LDAP 或 OIDC

5 讨论

5.1 更多 AuthN 策略

5.2 AuthN、AuthZ 和准入控制阶段

5.3 手动创建ServiceAccount

5.4 从客户端凭证中提取用户信息

5.5 用户模拟

6 总结

引用



1 引言

1.1 Kubernetes API 并查看会发生什么curl

作为开发人员,我们已经习惯了服务器与它交互, 例如,获取网站主页的内容:curl

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>curl example.com <span style="color:#999988"><em># Yes, this site really exists</em></span>
    ...
    <h1>Example Domain</h1>
    <p>This domain is <span style="color:#000000"><strong>for </strong></span>use <span style="color:#000000"><strong>in </strong></span>illustrative examples <span style="color:#000000"><strong>in </strong></span>documents. You may use this
    domain <span style="color:#000000"><strong>in </strong></span>literature without prior coordination or asking <span style="color:#000000"><strong>for </strong></span>permission.</p>
    <p><a <span style="color:#008080">href</span><span style="color:#000000"><strong>=</strong></span><span style="color:#dd1144">"https://www.iana.org/domains/example"</span><span style="color:#000000"><strong>></strong></span>More information...</a></p>
</html>
</code></span></span></span>

现在让我们对 Kubernetes API 服务器 () 进行类似的测试。 假设我们想列出集群中的所有命名空间kube-apiserver

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span><span style="color:#0086b3">export </span><span style="color:#008080">API_SERVER_URL</span><span style="color:#000000"><strong>=</strong></span>https://10.5.5.5:6443

<span style="color:#008080">$ </span>curl <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces
curl: <span style="color:#000000"><strong>(</strong></span>60<span style="color:#000000"><strong>)</strong></span> Peer Certificate issuer is not recognized.
...
If you<span style="color:#dd1144">'d like to turn off curl'</span>s verification of the certificate, use the <span style="color:#000080">-k</span> <span style="color:#000000"><strong>(</strong></span>or <span style="color:#000080">--insecure</span><span style="color:#000000"><strong>)</strong></span> option.
</code></span></span></span>

输出提醒我们,服务器正在使用无法识别的 证书(例如自签名),因此阻止了我们潜在的不安全。但 出于测试目的,我们可以关闭证书验证并继续:httpscurl

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>curl <span style="color:#000080">-k</span> <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces
<span style="color:#000000"><strong>{</strong></span>
  <span style="color:#dd1144">"kind"</span>: <span style="color:#dd1144">"Status"</span>,
  <span style="color:#dd1144">"apiVersion"</span>: <span style="color:#dd1144">"v1"</span>,
  <span style="color:#dd1144">"status"</span>: <span style="color:#dd1144">"Failure"</span>,
  <span style="color:#dd1144">"message"</span>: <span style="color:#dd1144">"namespaces is forbidden: User </span><span style="color:#dd1144">\"</span><span style="color:#dd1144">system:anonymous</span><span style="color:#dd1144">\"</span><span style="color:#dd1144"> cannot list resource </span><span style="color:#dd1144">\"</span><span style="color:#dd1144">namespaces</span><span style="color:#dd1144">\"</span><span style="color:#dd1144"> ..."</span>,
  <span style="color:#dd1144">"reason"</span>: <span style="color:#dd1144">"Forbidden"</span>,
  <span style="color:#dd1144">"details"</span>: <span style="color:#000000"><strong>{</strong></span> <span style="color:#dd1144">"kind"</span>: <span style="color:#dd1144">"namespaces"</span> <span style="color:#000000"><strong>}</strong></span>,
  <span style="color:#dd1144">"code"</span>: 403
<span style="color:#000000"><strong>}</strong></span>
</code></span></span></span>

好的,我们从服务器收到了一个回复,但它说我们作为用户 system:anonymous 不允许在 此 Kubernetes 集群,具有标准 HTTP 响应代码 403 (Unauthorized)。namespaces

1.2 AuthN 和 AuthZ

上述测试揭示了kube-apiserver

  • 首先,它标识请求的用户 (您是谁);
  • 然后,它确定此用户允许执行哪些操作(您拥有哪些权限);

正式

  • 前一个过程(识别您的身份)称为 AuthN,即身份验证的缩写;
  • 后一个过程(确定经过身份验证的用户拥有哪些权限)称为 AuthZ,即授权的缩写。

工作流如下所示:

图 1-1.处理客户端请求时的 AuthN 和 AuthZ

关于我们之前的测试用例:

  1. AuthN:我们没有提供任何用户凭证,因此 vanilla 身份验证 会失败;但是,取决于 是否允许匿名访问 :kube-apiserver

    • 1.1 不允许:直接返回 401 未授权(注意此状态 code 是一个长期存在的用词错误,因为它表示身份验证错误而不是授权错误,感谢 [4] 指出这一点);
    • 1.2 允许:以 system:anonymous 用户身份继续(我们的情况)并进入下一阶段 (AuthZ);
  2. AuthZ:检查是否有列出集群中 namespace 的权限system:anonymous

    • 2.1 否:返回 403 禁止(我们的情况);
    • 2.2 是:执行业务处理;

合理而清晰。

事实上,每个请求都应该与一个用户绑定,或者是 视为匿名请求。在这里,请求 可能来自集群内部或外部的进程,也可能来自人类用户 键入 kubectl 或 kubelets 在节点上,或者键入 control plane 的成员。 服务器中的 AuthN 模块将使用它提供的凭证对请求进行身份验证,或者 静态令牌、证书或外部托管的身份。kube-apiserver

AuthN 模块至关重要(而且不可避免地很复杂),因为它是整个系统的第一个守门人。 让我们简要描述一下本模块的要求。

1.3 AuthN 模块的要求

要成为像 Kubernetes 中那样实用的 AuthN 模块,至少必须满足以下属性:

  1. 支持人工用户和程序用户;
  2. 支持外部用户(例如部署在 OpenStack 或裸机系统中的应用程序)和内部用户(例如由 Kubernetes 集群本身);
  3. 支持常见的 AuthN 策略,例如 static token、bearer 令牌、X509 证书、OIDC(我们在此处不包括 BasicAuth,因为它具有 已从 Kubernetes 中删除,它基本上不提供任何 使用加密标准的安全机制);v1.19
  4. 支持同时启用 multipel AuthN 策略;
  5. 可扩展性:易于添加新的 AuthN 策略或逐步淘汰旧的 AuthN 策略;
  6. (可选)支持匿名访问(就像我们上面看到的情况一样)。system:anonymous

现在,如果您是一名软件工程师或工程师,在给出上述要求时,您将如何设计适合系统的 AuthN 解决方案

1.4 本文目的

本文尝试通过为 Kubernetes 设计一个 AuthN 模块来回答这个问题 我们自己 - 只是假装 Kuberntes 还没有。最终 设计将如下所示:

图 2-1.为 Kubernetes 设计的 AuthN 解决方案的工作流程

希望读者看完这篇文章后,能有更深入的了解 关于 Kubernetes AuthN stuff 的设计、配置和使用。

AuthN 和 AuthZ 密切相关, 请参考我们后面的博文 破解 Kubernetes RABC 授权模型 中的 AuthZ 部分。

2 解决方案设计

2.1 AuthN 链

我们的目标之一是能够同时支持多个 AuthN 策略,即 一些用户可以通过静态令牌访问,一些用户可以通过证书访问,还有一些用户可以通过证书访问 可能通过外部身份提供商(IdP,例如 Keystone)。

实际上,这意味着只要我们使用 一个策略成功了,我们应该跳过去尝试其余的策略; 只有当所有策略都已尝试并失败(并且匿名访问被禁用)时,我们 应将此视为无效用户。这表明了以下链设计, 具有上述短路性能

图 2-1.AuthN 链的短路特性

链式设计还使可扩展性变得容易: 只需在链中添加或删除给定的 AuthN 策略。

2.2 区分两种用户

另一个要求是支持不同类型的 API 用户,如下所示:

图 2-2.内部和外部 Kubernetes API 用户

从 API 用户是否在 Kubernetes 集群内部来判断,我们可以 将用户分为以下两种类型:

  1. Kubernetes 托管用户:由 Kubernetes 集群本身并由集群内应用程序使用, 我们将它们命名为 “服务账户”;

  2. 非 Kubernetes 托管用户:Kubernetes 集群外部的用户,例如

    • 具有集群管理员提供的静态令牌或证书的用户;
    • 用户通过 Keystone、Google 帐户和 LDAP 等外部身份提供商进行身份验证。

此差异的实现含义:

  • 前者是原生的 Kubernetes 对象,所以我们需要为它们定义一个规范(数据模型);
  • 后者不是 Kubernetes 对象,因此不会有它们的规范。

2.2.1 简介ServiceAccount

根据我们的设计,Servicea 帐户通常会以自动方式为部署在集群中的应用程序创建,使用 通过应用程序 (pod) 访问 .kube-apiserverkube-apiserver

规范简介:将账户名绑定到 Token 以 Kubernetes 格式存储:ServiceAccountSecret

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>type</strong></span> ServiceAccount <span style="color:#000000"><strong>struct</strong></span> {
    metav1<span style="color:#000000"><strong>.</strong></span>TypeMeta
    metav1<span style="color:#000000"><strong>.</strong></span>ObjectMeta

    Secrets []ObjectReference          <span style="color:#999988"><em>// To be used by pods running using this ServiceAccount, which</em></span>
                                       <span style="color:#999988"><em>// holds the public CA of kube-apiserver and a signed JWT token</em></span>
    AutomountServiceAccountToken <span style="color:#000000"><strong>*</strong></span><span style="color:#445588"><strong>bool</strong></span> <span style="color:#999988"><em>// Whether to mount the secret into related pods automatically</em></span>
}
</code></span></span></span>

如果模型已经存在于 Kubernetes 模型中,Secret

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/staging/src/k8s.io/api/core/v1/types.go#L6005</em></span>

<span style="color:#000000"><strong>type</strong></span> Secret <span style="color:#000000"><strong>struct</strong></span> {
    metav1<span style="color:#000000"><strong>.</strong></span>TypeMeta
    metav1<span style="color:#000000"><strong>.</strong></span>ObjectMeta

    Data <span style="color:#000000"><strong>map</strong></span>[<span style="color:#445588"><strong>string</strong></span>][]<span style="color:#445588"><strong>byte</strong></span> <span style="color:#999988"><em>// base64 encoded, hold the ServiceAccount token</em></span>
}
</code></span></span></span>

2.2.2 引入 normalUser

外部用户不是 Kubernetes 对象,因此无需为其创建数据模型。

但是,为了与后续的 AuthZ 模块交互,我们必须具备 从给定的用户令牌中推断出用户和组信息。 这将在下一节中详细说明。

2.3 促进 AuthZ

如果只考虑 AuthN,我们的链设计就足够了。但正如 说,如果我们想更好地将 AuthN 模块集成到 系统,我们应该多做一点 - 具体来说,我们需要喂养一些 important 信息添加到后续的 AuthZ 模块中。

AuthZ 检查给定使用者具有的权限,其中使用者可以是用户、用户组或角色。 问题是:请求头只包含原始 Token 或 Certificates, 可以被 AuthN 模块识别,但对 AuthZ 模块毫无用处,后者更喜欢用户 / 组 / 角色信息来完成其任务。所以

  • 为了帮我们的相邻邻居一个忙,我们(AuthN 模块)应该将 已验证凭据到相应的用户/组/角色表示形式。 为此,我们引入了以下接口:

    <span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code>  <span style="color:#999988"><em>// Get user information from an authenticated token or credential.</em></span>
      <span style="color:#999988"><em>// Should be implemented by each authenticator.</em></span>
      <span style="color:#000000"><strong>type</strong></span> Info <span style="color:#000000"><strong>interface</strong></span> {
          GetName() <span style="color:#445588"><strong>string</strong></span>
          GetUID() <span style="color:#445588"><strong>string</strong></span>
          GetGroups() []<span style="color:#445588"><strong>string</strong></span>
          GetExtra() <span style="color:#000000"><strong>map</strong></span>[<span style="color:#445588"><strong>string</strong></span>][]<span style="color:#445588"><strong>string</strong></span>
      }
    </code></span></span>
  • 然后,我们可以将用户信息插入到请求上下文中, 我们的邻居可以稍后从上下文中获取。

  • 此外,一旦 AuthnN 完成,标头将毫无用处,因此我们可以安全地删除它。Authorization

通过上述改进,更新的流程将如下所示:

图 2-3.将 AuthN 凭证转换为用户信息,以便于后续处理(例如 RBAC AuthZ)

2.4 假设的 AuthN 工作流

完成上述所有准备工作后,我们已准备好描述我们的 AuthN 工作流程。 收到客户端请求时,

  1. 从请求标头中提取 AuthN 凭据;
  2. 根据验证器列表对请求进行身份验证;

    • 失败时:返回 “401 Unauthorized”
    • 成功时:将用户信息添加到请求上下文中,并从请求头中删除 AuthN 信息;转到下一个处理;
  3. 如果所有验证器都失败并且启用了匿名访问,请尝试匿名访问;

2.5 支持特定的 AuthN 策略

部分将展示如何为每个策略实施验证器。 但是,在此之前,让我们简要描述一下 “bearer token” 机制。

Bearer 身份验证是一种 HTTP 身份验证方案。客户端携带令牌 在向服务器发出请求时的标头中,以 如下:Authorization

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code>Authorization: Bearer <token>
</code></span></span></span>

不记名令牌方案最初是作为 OAuth 2.0 的一部分创建的。 RFC 6750 的 S Git,但也可以单独使用。所以 AuthN 策略(如静态令牌身份验证)仅放置 他们的特定 token 通过这种机制进入 field。有了这个 说到这里,我们先看看如何实现静态 Token Authenticator。<token>

2.6.1 支持静态令牌(适用于集群外用户/应用程序)

静态令牌是一种简单的机制,集群管理员会在其中生成一些 有效令牌 (abitrary strings) 并分配给 API 用户。用户必须携带此类 令牌。

请注意,在这种情况下,令牌由管理员生成,他/她必须 还要提供相应的用户信息,否则 Kubernetes AuthZ 模块将没有信息来做权限判断。 说到这里,这就是我们的简单设计,

  1. 定义静态 Token 格式:<token,user,uid,gid>

    <span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code> <span style="color:#008080">$ </span><span style="color:#0086b3">cat</span> /etc/kubernetes/static-tokens.csv <span style="color:#999988"><em># The last field (group id) is optional</em></span>
     31ada4fd-adec-460c,alice,123,456
     22a38432-8bcb-cdcf,bob,124,457
     ...
    </code></span></span>
  2. 添加一个 CLI 选项来加载令牌文件,例如 --token-auth-file=/etc/kubernetes/static-tokens.csvkube-apiserver

整个工作流程如下所示:

图 3-1.支持静态令牌 AuthN 策略

  1. Admin:创建一个 CSV 令牌身份验证文件,提供给 Kubernetes 集群;
  2. Admin:从这个开始是配置,读取并缓存内存中的信息;kube-apiserver
  3. 管理员:将令牌分配给外部用户和/或应用程序;
  4. 客户端:发送请求时,在 bearer token 字段(Authorization: Bearer TOKEN)中携带 token;
  5. Server () AuthN:根据 令牌列表(如果已验证,则检索 AuthZ 的用户信息kube-apiserver);

上述步骤成功后,AuthZ 将启动(使用用户信息 由 AuthN 提供),但这超出了本文的范围。查看我们的 发布破解 Kubernetes RABC 授权模型 的 AuthZ 部分。

2.6.2 Support X509 certificate (for out-of-cluster users/apps)

Similar as static token, we can support X509 client certificate.

图 3-2.支持 X509 证书 AuthN 策略

  1. 管理员:准备证书颁发机构 (CA),用于验证客户端证书;
  2. 管理员:从这个 is config 开始(带有一个新选项 --client-ca-file=FILEkube-apiserver);
  3. 管理员:向外部用户和/或应用程序颁发带有根 CA 的客户端证书;

    这些 X509 客户端证书内部已包含用户和组信息,例如: 使用openssl

    <span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code> <span style="color:#999988"><em># For the username "jbeda", belonging to two groups, "app1" and "app2".</em></span>
     <span style="color:#008080">$ </span>openssl req <span style="color:#000080">-new</span> <span style="color:#000080">-key</span> jbeda.pem <span style="color:#000080">-out</span> jbeda-csr.pem <span style="color:#000080">-subj</span> <span style="color:#dd1144">"/CN=jbeda/O=app1/O=app2"</span>
    </code></span></span>
    • CN (公用名 (common name):请求的用户名。
    • O (organization):用户的组成员资格。要包含用户的多个组成员资格,请在证书中包含多个组织字段。
  4. 客户端:发送请求时,将证书放在 TLS 字段中;
  5. 服务器 ():验证客户端证书 针对根 CA 的 API 进行验证。如果提供并验证了客户端证书,请提取信息。kube-apiserver

2.6.3 支持(主要针对集群内应用程序)ServiceAccount

以上两种策略需要一个(人工)管理员来生成和 分发客户端凭据,这显然对本机不友好 部署在 Kubernetes 集群中的应用程序。对于后一种情况,有 应该是一种自动方式。这就是我们引入 abstration 的原因。ServiceAccount

对于实现,

  1. 它还使用已签名的不记名令牌来验证请求;
  2. 仍然使用 X509 证书,但可以使用独立的 CA 进行配置,以区别于外部用户/应用程序;

    --service-account-key-file=<sa-key.pem>:包含用于对持有者令牌进行签名的 PEM 编码密钥的文件。 如果未指定,请使用 的 TLS 私有密钥。kube-apiserver

图 3-3.支持 X509 AuthN 策略

客户端证书将由 control plane 自动签名,并且 由 control plane 挂载到已知位置的相关 Pod 组件(ServiceAccount 准入控制器)。

工作流程: .1 -> 2 -> 3b -> 4b -> 5

服务账户持有者令牌主要用于集群内应用程序, 但它们在集群外部使用是完全有效的,并且可以用于为希望对话的长期工作创建身份 添加到 Kubernetes API。

关于用户信息收集, 服务帐户使用用户名 , 并分配给组 和 。system:serviceaccount:<ns>:<sa>system:serviceaccountssystem:serviceaccounts:<ns>

2.6.4 支持匿名用户

这很容易,只要我们为匿名用户提供专用的用户/组关联即可 这样以后的 AuthZ 模块就可以正常工作了:

  • 用户:system:anonymous
  • 群:system:unauthenticated

2.6.5 连锁

以类似的方式,我们可以支持其他类型的 AuthN 策略,例如 OpenID Connect (OIDC),请求标头 ()。 通过所有这些策略,我们解决方案的最终架构和工作流程 将如下所示:X-Remote-xxx

图 3-4.设计的 AuthN 解决方案的工作流程

以及验证请求时的 psudo-code:kube-apiserver

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>func</strong></span> AuthN(nextHandler, req) {
    <span style="color:#000000"><strong>for</strong></span> h in authenticator<span style="color:#000000"><strong>.</strong></span>handlers {
        <span style="color:#000000"><strong>if</strong></span> resp, ok <span style="color:#000000"><strong>:=</strong></span> h<span style="color:#000000"><strong>.</strong></span>AuthenticateRequest(req); ok {
           req<span style="color:#000000"><strong>.</strong></span>Header<span style="color:#000000"><strong>.</strong></span>Del(<span style="color:#dd1144">"Authorization"</span>)  <span style="color:#999988"><em>// not required anymore in case of a successful authentication.</em></span>
           req <span style="color:#000000"><strong>=</strong></span> req<span style="color:#000000"><strong>.</strong></span>Context<span style="color:#000000"><strong>.</strong></span>Add(resp<span style="color:#000000"><strong>.</strong></span>User) <span style="color:#999988"><em>// put user info into context</em></span>

           nextHandler<span style="color:#000000"><strong>.</strong></span>ServeHTTP(w, req)    <span style="color:#999988"><em>// go to the next handler (e.g. AuthZ)</em></span>
           <span style="color:#000000"><strong>return</strong></span>
        }
    }

    <span style="color:#000000"><strong>if</strong></span> allowAnonymousAccess {
        req<span style="color:#000000"><strong>.</strong></span>Header<span style="color:#000000"><strong>.</strong></span>Del(<span style="color:#dd1144">"Authorization"</span>)
        req <span style="color:#000000"><strong>=</strong></span> req<span style="color:#000000"><strong>.</strong></span>Context<span style="color:#000000"><strong>.</strong></span>Add(<span style="color:#dd1144">"system:anonymous"</span>)
        nextHandler<span style="color:#000000"><strong>.</strong></span>ServeHTTP(w, req)    <span style="color:#999988"><em>// go to the next handler (e.g. AuthZ)</em></span>
        <span style="color:#000000"><strong>return</strong></span>
    }

    <span style="color:#999988"><em>// Some error handling, then return 401</em></span>
    <span style="color:#000000"><strong>return</strong></span> <span style="color:#dd1144">"401 Unauthorized"</span>
}
</code></span></span></span>

毫不奇怪,这个手工制作的解决方案只是一个简化版本 的 Kubernetes 中附带的那个。

3 实施

如果您想现在进行一些动手测试,您可以跳过本节并直接进入 setion 4。

而且,在不让这篇文章写得太长的情况下,我们 只需给出一些 vanilla Kubernetes AuthN 的实现描述即可。 基于 Kubernetes 的代码 。v1.23

3.1 AuthN 选项注册kube-apiserver

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/pkg/kubeapiserver/options/authentication.go#L48</em></span>

<span style="color:#999988"><em>// All build-in authentication options for API Server</em></span>
<span style="color:#000000"><strong>type</strong></span> BuiltInAuthenticationOptions <span style="color:#000000"><strong>struct</strong></span> {
    APIAudiences    []<span style="color:#445588"><strong>string</strong></span>
    Anonymous       <span style="color:#000000"><strong>*</strong></span>AnonymousAuthenticationOptions
    BootstrapToken  <span style="color:#000000"><strong>*</strong></span>BootstrapTokenAuthenticationOptions
    ClientCert      <span style="color:#000000"><strong>*</strong></span>genericoptions<span style="color:#000000"><strong>.</strong></span>ClientCertAuthenticationOptions
    OIDC            <span style="color:#000000"><strong>*</strong></span>OIDCAuthenticationOptions
    RequestHeader   <span style="color:#000000"><strong>*</strong></span>genericoptions<span style="color:#000000"><strong>.</strong></span>RequestHeaderAuthenticationOptions
    ServiceAccounts <span style="color:#000000"><strong>*</strong></span>ServiceAccountAuthenticationOptions
    TokenFile       <span style="color:#000000"><strong>*</strong></span>TokenFileAuthenticationOptions
    WebHook         <span style="color:#000000"><strong>*</strong></span>WebHookAuthenticationOptions

    TokenSuccessCacheTTL time<span style="color:#000000"><strong>.</strong></span>Duration
    TokenFailureCacheTTL time<span style="color:#000000"><strong>.</strong></span>Duration
}

<span style="color:#999988"><em>// WithAll set default value for every build-in authentication option</em></span>
<span style="color:#000000"><strong>func</strong></span> (o <span style="color:#000000"><strong>*</strong></span>BuiltInAuthenticationOptions) WithAll() <span style="color:#000000"><strong>*</strong></span>BuiltInAuthenticationOptions {
    <span style="color:#000000"><strong>return</strong></span> o<span style="color:#000000"><strong>.</strong></span>
        WithAnonymous()<span style="color:#000000"><strong>.</strong></span>
        WithBootstrapToken()<span style="color:#000000"><strong>.</strong></span>
        WithClientCert()<span style="color:#000000"><strong>.</strong></span>
        WithOIDC()<span style="color:#000000"><strong>.</strong></span>
        WithRequestHeader()<span style="color:#000000"><strong>.</strong></span>
        WithServiceAccounts()<span style="color:#000000"><strong>.</strong></span>
        WithTokenFile()<span style="color:#000000"><strong>.</strong></span>
        WithWebHook()
}

<span style="color:#999988"><em>// AddFlags returns flags of authentication for a API Server</em></span>
<span style="color:#000000"><strong>func</strong></span> (o <span style="color:#000000"><strong>*</strong></span>BuiltInAuthenticationOptions) AddFlags(fs <span style="color:#000000"><strong>*</strong></span>pflag<span style="color:#000000"><strong>.</strong></span>FlagSet) {
    <span style="color:#999988"><em>// register options, e.g. --token-auth-file</em></span>
}
</code></span></span></span>

3.2 Kubernetes 认证链

图 1-1.

如上所述,Kubernetes 实现了比我们的玩具解决方案更多的 AuthN 策略,

  1. 支持请求标头身份验证(例如 headers)X-Remote-UserX-Remote-Group
  2. 将各种 bearer token 方法归为一个通用方法

但从本质上讲,它与我们的玩具设计相似。一些代码:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// New returns an authenticator.Request or an error that supports the standard</em></span>
<span style="color:#999988"><em>// Kubernetes authentication mechanisms.</em></span>
Config.New() <span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/pkg/kubeapiserver/authenticator/config.go#L94</em></span>
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|</strong></span><span style="color:#999988"><em>// Request header method</em></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> config.RequestHeaderConfig
 <span style="color:#000000"><strong>|</strong></span>   requestHeaderAuthenticator <span style="color:#000000"><strong>:=</strong></span> headerrequest.NewDynamicVerifyOptionsSecure()
 <span style="color:#000000"><strong>|</strong></span>   authenticators.append(requestHeaderAuthenticator)
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|</strong></span><span style="color:#999988"><em>// X509 methods</em></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> config.ClientCAContentProvider
 <span style="color:#000000"><strong>|</strong></span>   certAuth <span style="color:#000000"><strong>:=</strong></span> x509.NewDynamic()
 <span style="color:#000000"><strong>|</strong></span>   authenticators.append(certAuth)
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|</strong></span><span style="color:#999988"><em>// ================= Bearer token methods starts ===========================</em></span>
 <span style="color:#000000"><strong>|</strong></span> <span style="color:#999988"><em>// Token Auth File</em></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> config.TokenAuthFile <span style="color:#999988"><em>// --token-auth-file</em></span>
 <span style="color:#000000"><strong>|</strong></span>      tokenAuth <span style="color:#000000"><strong>:=</strong></span> newAuthenticatorFromTokenFile(config.TokenAuthFile)
 <span style="color:#000000"><strong>|</strong></span>      tokenAuthenticators.append(tokenAuth)
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|</strong></span> <span style="color:#999988"><em>// ServiceAccount key files</em></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> config.ServiceAccountKeyFiles <span style="color:#999988"><em>// --service-account-key-file</em></span>
 <span style="color:#000000"><strong>|</strong></span>     serviceAccountAuth <span style="color:#000000"><strong>:=</strong></span> newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles)
 <span style="color:#000000"><strong>|</strong></span>     tokenAuthenticators.append(serviceAccountAuth)
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|</strong></span> <span style="color:#999988"><em>// ServiceAccount issuers</em></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> config.ServiceAccountIssuers  <span style="color:#999988"><em>// --service-account-issuers</em></span>
 <span style="color:#000000"><strong>|</strong></span>     serviceAccountAuth <span style="color:#000000"><strong>:=</strong></span> newServiceAccountAuthenticator(config.ServiceAccountIssuers, config.ServiceAccountKeyFiles)
 <span style="color:#000000"><strong>|</strong></span>     tokenAuthenticators.append(serviceAccountAuth)
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|</strong></span> <span style="color:#999988"><em>// Bootstrap tokens</em></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> config.BootstrapToken
 <span style="color:#000000"><strong>|</strong></span>     tokenAuthenticators.append(config.BootstrapTokenAuthenticator)
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|</strong></span> <span style="color:#999988"><em>// OIDC</em></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> config.OIDC
 <span style="color:#000000"><strong>|</strong></span>     tokenAuthenticators.append(oidcAuth)
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|</strong></span> <span style="color:#999988"><em>// Webhok token auth confi file</em></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> config.WebhookTokenAuthnConfigFile
 <span style="color:#000000"><strong>|</strong></span>     webhookTokenAuth <span style="color:#000000"><strong>:=</strong></span> newWebhookTokenAuthenticator(config)
 <span style="color:#000000"><strong>|</strong></span>     tokenAuthenticators.append(webhookTokenAuth)
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> len(tokenAuthenticators) <span style="color:#000000"><strong>></strong></span> <span style="color:#009999">0</span>
 <span style="color:#000000"><strong>|</strong></span>     tokenAuth <span style="color:#000000"><strong>:=</strong></span> tokenunion.New(tokenAuthenticators...) <span style="color:#999988"><em>// Union the token authenticators (and cache it optionally)</em></span>
 <span style="color:#000000"><strong>|</strong></span>     authenticators.append(bearertoken.New(tokenAuth),   <span style="color:#999988"><em>// general bearertoken wrapper</em></span>
 <span style="color:#000000"><strong>|</strong></span>         websocket.NewProtocolAuthenticator(tokenAuth))  <span style="color:#999988"><em>//</em></span>
 <span style="color:#000000"><strong>|</strong></span>     securityDefinitions[<span style="color:#dd1144">"BearerToken"</span>] <span style="color:#000000"><strong>=</strong></span> <span style="color:#000000"><strong>&</strong></span>spec.SecurityScheme{
 <span style="color:#000000"><strong>|</strong></span>             Type<span style="color:#000000"><strong>:</strong></span>        <span style="color:#dd1144">"apiKey"</span>,
 <span style="color:#000000"><strong>|</strong></span>             Name<span style="color:#000000"><strong>:</strong></span>        <span style="color:#dd1144">"authorization"</span>,
 <span style="color:#000000"><strong>|</strong></span>             In<span style="color:#000000"><strong>:</strong></span>          <span style="color:#dd1144">"header"</span>,
 <span style="color:#000000"><strong>|</strong></span>             Description<span style="color:#000000"><strong>:</strong></span> <span style="color:#dd1144">"Bearer Token authentication"</span>,
 <span style="color:#000000"><strong>|</strong></span>     }
 <span style="color:#000000"><strong>|</strong></span><span style="color:#999988"><em>// ================= Bearer token methods ends ===========================</em></span>
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|</strong></span>  authenticator <span style="color:#000000"><strong>:=</strong></span> <span style="color:#000000"><strong>union</strong></span>.New(authenticators...)
 <span style="color:#000000"><strong>|</strong></span>  authenticator <span style="color:#000000"><strong>=</strong></span> group.NewAuthenticatedGroupAdder(authenticator)
 <span style="color:#000000"><strong>|</strong></span>
 <span style="color:#000000"><strong>|-</strong></span><span style="color:#000000"><strong>if</strong></span> config.Anonymous
        authenticator.append(anonymous.NewAuthenticator)
</code></span></span></span>

3.3 对请求进行身份验证

当请求到达时,将调用它,它尝试对给定的 请求作为用户,然后将用户信息存储到请求的上下文中。 成功后,将从请求和下一个处理程序中删除 “Authorization” 标头 来提供请求:WithAuthentication()

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.0/staging/src/k8s.io/apiserver/pkg/endpoints/filters/authentication.go#L41</em></span>

<span style="color:#000000"><strong>func</strong></span> WithAuthentication(handler http<span style="color:#000000"><strong>.</strong></span>Handler, auth authenticator<span style="color:#000000"><strong>.</strong></span>Request) http<span style="color:#000000"><strong>.</strong></span>Handler {
    <span style="color:#000000"><strong>return</strong></span> withAuthentication(handler, auth, failed, apiAuds, recordAuthMetrics)
}

<span style="color:#000000"><strong>func</strong></span> withAuthentication(handler http<span style="color:#000000"><strong>.</strong></span>Handler, auth authenticator<span style="color:#000000"><strong>.</strong></span>Request, <span style="color:#000000"><strong>...</strong></span>) http<span style="color:#000000"><strong>.</strong></span>Handler {
    <span style="color:#000000"><strong>return</strong></span> http<span style="color:#000000"><strong>.</strong></span>HandlerFunc(<span style="color:#000000"><strong>func</strong></span>(w http<span style="color:#000000"><strong>.</strong></span>ResponseWriter, req <span style="color:#000000"><strong>*</strong></span>http<span style="color:#000000"><strong>.</strong></span>Request) {
        resp, ok <span style="color:#000000"><strong>:=</strong></span> auth<span style="color:#000000"><strong>.</strong></span>AuthenticateRequest(req) <span style="color:#999988"><em>// iterate over the authenticator list, return ok if anyone succeeds</em></span>
        <span style="color:#000000"><strong>if</strong></span> <span style="color:#000000"><strong>!</strong></span>ok {
            failed<span style="color:#000000"><strong>.</strong></span>ServeHTTP()
            <span style="color:#000000"><strong>return</strong></span>
        }

        req<span style="color:#000000"><strong>.</strong></span>Header<span style="color:#000000"><strong>.</strong></span>Del(<span style="color:#dd1144">"Authorization"</span>) <span style="color:#999988"><em>// not required anymore in case of a successful authentication.</em></span>
        req<span style="color:#000000"><strong>.</strong></span>Context<span style="color:#000000"><strong>.</strong></span>Add(resp<span style="color:#000000"><strong>.</strong></span>User)

        handler<span style="color:#000000"><strong>.</strong></span>ServeHTTP(w, req)
    })
}
</code></span></span></span>

auth.AuthenticateRequest(req)将遍历已注册的 Authenticator 列表,并且 如果有人成功,则返回 OK。

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/staging/src/k8s.io/apiserver/pkg/authentication/request/union/union.go#L53</em></span>

<span style="color:#999988"><em>// unionAuthRequestHandler authenticates requests using a chain of authenticator.Requests</em></span>
<span style="color:#000000"><strong>type</strong></span> unionAuthRequestHandler <span style="color:#000000"><strong>struct</strong></span> {
    Handlers []authenticator<span style="color:#000000"><strong>.</strong></span>Request
    FailOnError <span style="color:#445588"><strong>bool</strong></span> <span style="color:#999988"><em>// determines whether an error returns short-circuits the chain</em></span>
}

<span style="color:#999988"><em>// Authenticate the request using a chain of authenticator.Request objects.</em></span>
<span style="color:#000000"><strong>func</strong></span> (authHandler <span style="color:#000000"><strong>*</strong></span>unionAuthRequestHandler) AuthenticateRequest(req <span style="color:#000000"><strong>*</strong></span>http<span style="color:#000000"><strong>.</strong></span>Request) (<span style="color:#000000"><strong>*</strong></span>authenticator<span style="color:#000000"><strong>.</strong></span>Response, <span style="color:#445588"><strong>bool</strong></span>, <span style="color:#445588"><strong>error</strong></span>) {
    <span style="color:#000000"><strong>for</strong></span> _, h <span style="color:#000000"><strong>:=</strong></span> <span style="color:#000000"><strong>range</strong></span> authHandler<span style="color:#000000"><strong>.</strong></span>Handlers {
        resp, ok, err <span style="color:#000000"><strong>:=</strong></span> h<span style="color:#000000"><strong>.</strong></span>AuthenticateRequest(req)
        <span style="color:#000000"><strong>if</strong></span> ok {
            <span style="color:#000000"><strong>return</strong></span> resp, ok, err
        }
    }

    <span style="color:#000000"><strong>return</strong></span> <span style="color:#008080">nil</span>, <span style="color:#008080">false</span>, utilerrors<span style="color:#000000"><strong>.</strong></span>NewAggregate(errlist)
}
</code></span></span></span>

让我们看看两个特定的身份验证器。

3.3.1 静态令牌认证

静态令牌验证器以及许多其他基于令牌的验证器由 实现中的通用 Bearer Token Authenticator:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/staging/src/k8s.io/apiserver/pkg/authentication/request/bearertoken/bearertoken.go#L37</em></span>
<span style="color:#000000"><strong>func</strong></span> (a <span style="color:#000000"><strong>*</strong></span>Authenticator) AuthenticateRequest(req <span style="color:#000000"><strong>*</strong></span>http<span style="color:#000000"><strong>.</strong></span>Request) (<span style="color:#000000"><strong>*</strong></span>authenticator<span style="color:#000000"><strong>.</strong></span>Response, <span style="color:#445588"><strong>bool</strong></span>, <span style="color:#445588"><strong>error</strong></span>) {
    auth <span style="color:#000000"><strong>:=</strong></span> strings<span style="color:#000000"><strong>.</strong></span>TrimSpace(req<span style="color:#000000"><strong>.</strong></span>Header<span style="color:#000000"><strong>.</strong></span>Get(<span style="color:#dd1144">"Authorization"</span>))
    parts <span style="color:#000000"><strong>:=</strong></span> strings<span style="color:#000000"><strong>.</strong></span>SplitN(auth, <span style="color:#dd1144">" "</span>, <span style="color:#009999">3</span>)
    token <span style="color:#000000"><strong>:=</strong></span> parts[<span style="color:#009999">1</span>]

    resp, ok, err <span style="color:#000000"><strong>:=</strong></span> a<span style="color:#000000"><strong>.</strong></span>auth<span style="color:#000000"><strong>.</strong></span>AuthenticateToken(req<span style="color:#000000"><strong>.</strong></span>Context(), token)
    <span style="color:#000000"><strong>if</strong></span> ok {
        req<span style="color:#000000"><strong>.</strong></span>Header<span style="color:#000000"><strong>.</strong></span>Del(<span style="color:#dd1144">"Authorization"</span>)
    }

    <span style="color:#999988"><em>// If the token authenticator didn't error, provide a default error</em></span>
    <span style="color:#000000"><strong>if</strong></span> <span style="color:#000000"><strong>!</strong></span>ok <span style="color:#000000"><strong>&&</strong></span> err <span style="color:#000000"><strong>==</strong></span> <span style="color:#008080">nil</span> {
        err <span style="color:#000000"><strong>=</strong></span> invalidToken
    }

    <span style="color:#000000"><strong>return</strong></span> resp, ok, err
}
</code></span></span></span>

a.auth.AuthenticateRequest()将进一步委托给真正的静态 Token 鉴权器:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/staging/src/k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go#L28</em></span>
<span style="color:#000000"><strong>type</strong></span> Token <span style="color:#000000"><strong>interface</strong></span> {
    AuthenticateToken(ctx context<span style="color:#000000"><strong>.</strong></span>Context, token <span style="color:#445588"><strong>string</strong></span>) (<span style="color:#000000"><strong>*</strong></span>Response, <span style="color:#445588"><strong>bool</strong></span>, <span style="color:#445588"><strong>error</strong></span>)
}

<span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile/tokenfile.go#L93</em></span>
<span style="color:#000000"><strong>func</strong></span> (a <span style="color:#000000"><strong>*</strong></span>TokenAuthenticator) AuthenticateToken(ctx context<span style="color:#000000"><strong>.</strong></span>Context, value <span style="color:#445588"><strong>string</strong></span>) (<span style="color:#000000"><strong>*</strong></span>authenticator<span style="color:#000000"><strong>.</strong></span>Response, <span style="color:#445588"><strong>bool</strong></span>, <span style="color:#445588"><strong>error</strong></span>) {
    user, ok <span style="color:#000000"><strong>:=</strong></span> a<span style="color:#000000"><strong>.</strong></span>tokens[value]
    <span style="color:#000000"><strong>if</strong></span> <span style="color:#000000"><strong>!</strong></span>ok {
        <span style="color:#000000"><strong>return</strong></span> <span style="color:#008080">nil</span>, <span style="color:#008080">false</span>, <span style="color:#008080">nil</span>
    }

    <span style="color:#000000"><strong>return</strong></span> <span style="color:#000000"><strong>&</strong></span>authenticator<span style="color:#000000"><strong>.</strong></span>Response{User<span style="color:#000000"><strong>:</strong></span> user}, <span style="color:#008080">true</span>, <span style="color:#008080">nil</span> <span style="color:#999988"><em>// User: type user.Info</em></span>
}
</code></span></span></span>

检索令牌信息的位置:User

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/staging/src/k8s.io/apiserver/pkg/authentication/user/user.go#L20</em></span>

<span style="color:#999988"><em>// Info describes a user that has been authenticated to the system.</em></span>
<span style="color:#000000"><strong>type</strong></span> Info <span style="color:#000000"><strong>interface</strong></span> {
    GetName() <span style="color:#445588"><strong>string</strong></span>
    GetUID() <span style="color:#445588"><strong>string</strong></span>
    GetGroups() []<span style="color:#445588"><strong>string</strong></span>
    GetExtra() <span style="color:#000000"><strong>map</strong></span>[<span style="color:#445588"><strong>string</strong></span>][]<span style="color:#445588"><strong>string</strong></span>
}
</code></span></span></span>

令牌文件 authenticator 在启动时读取 3 元组 CSV 文件, 并将此信息存储在内存中。<token,user,userid>kube-apiserver

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// NewCSV returns a TokenAuthenticator, populated from a CSV file.</em></span>
<span style="color:#999988"><em>// The CSV file must contain records in the format "token,username,useruid"</em></span>
<span style="color:#000000"><strong>func</strong></span> NewCSV(path <span style="color:#445588"><strong>string</strong></span>) (<span style="color:#000000"><strong>*</strong></span>TokenAuthenticator, <span style="color:#445588"><strong>error</strong></span>) {
    <span style="color:#000000"><strong>...</strong></span>
}
</code></span></span></span>

所以这里它只检查给定的 token 是否在 token 列表,如果是,则提取字段并放入响应中(以便稍后在外部验证器可以将 它进入请求的上下文以供后续 AuthZ 使用)。user

3.3.2 X509 身份验证器

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.0/staging/src/k8s.io/apiserver/pkg/authentication/request/x509/x509.go#L198</em></span>

<span style="color:#000000"><strong>func</strong></span> (a <span style="color:#000000"><strong>*</strong></span>Verifier) AuthenticateRequest(req <span style="color:#000000"><strong>*</strong></span>http<span style="color:#000000"><strong>.</strong></span>Request) (<span style="color:#000000"><strong>*</strong></span>authenticator<span style="color:#000000"><strong>.</strong></span>Response, <span style="color:#445588"><strong>bool</strong></span>, <span style="color:#445588"><strong>error</strong></span>) {
    <span style="color:#000000"><strong>...</strong></span>
    req<span style="color:#000000"><strong>.</strong></span>TLS<span style="color:#000000"><strong>.</strong></span>PeerCertificates[<span style="color:#009999">0</span>]<span style="color:#000000"><strong>.</strong></span>Verify();
    verifySubject(req<span style="color:#000000"><strong>.</strong></span>TLS<span style="color:#000000"><strong>.</strong></span>PeerCertificates[<span style="color:#009999">0</span>]<span style="color:#000000"><strong>.</strong></span>Subject)

    <span style="color:#000000"><strong>return</strong></span> a<span style="color:#000000"><strong>.</strong></span>auth<span style="color:#000000"><strong>.</strong></span>AuthenticateRequest(req)
}

<span style="color:#000000"><strong>func</strong></span> (a <span style="color:#000000"><strong>*</strong></span>Authenticator) AuthenticateRequest(req <span style="color:#000000"><strong>*</strong></span>http<span style="color:#000000"><strong>.</strong></span>Request) (<span style="color:#000000"><strong>*</strong></span>authenticator<span style="color:#000000"><strong>.</strong></span>Response, <span style="color:#445588"><strong>bool</strong></span>, <span style="color:#445588"><strong>error</strong></span>) {
    req<span style="color:#000000"><strong>.</strong></span>TLS<span style="color:#000000"><strong>.</strong></span>PeerCertificates[<span style="color:#009999">0</span>]<span style="color:#000000"><strong>.</strong></span>NotAfter<span style="color:#000000"><strong>.</strong></span>Sub(time<span style="color:#000000"><strong>.</strong></span>Now())

    chains <span style="color:#000000"><strong>:=</strong></span> req<span style="color:#000000"><strong>.</strong></span>TLS<span style="color:#000000"><strong>.</strong></span>PeerCertificates[<span style="color:#009999">0</span>]<span style="color:#000000"><strong>.</strong></span>Verify()
    <span style="color:#000000"><strong>for</strong></span> _, chain <span style="color:#000000"><strong>:=</strong></span> <span style="color:#000000"><strong>range</strong></span> chains {
        user, ok <span style="color:#000000"><strong>:=</strong></span> a<span style="color:#000000"><strong>.</strong></span>user<span style="color:#000000"><strong>.</strong></span>User(chain)
        <span style="color:#000000"><strong>if</strong></span> ok {
            <span style="color:#000000"><strong>return</strong></span> user, ok, err
        }
    }

    <span style="color:#000000"><strong>return</strong></span> err
}
</code></span></span></span>

3.4 和ServiceAccountSecret

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/staging/src/k8s.io/api/core/v1/types.go#L4600</em></span>

<span style="color:#999988"><em>// ServiceAccount binds together:</em></span>
<span style="color:#999988"><em>// * a name, understood by users, and perhaps by peripheral systems, for an identity</em></span>
<span style="color:#999988"><em>// * a principal that can be authenticated and authorized</em></span>
<span style="color:#999988"><em>// * a set of secrets</em></span>
<span style="color:#000000"><strong>type</strong></span> ServiceAccount <span style="color:#000000"><strong>struct</strong></span> {
    metav1<span style="color:#000000"><strong>.</strong></span>TypeMeta
    metav1<span style="color:#000000"><strong>.</strong></span>ObjectMeta

    <span style="color:#999988"><em>// list of secrets allowed to be used by pods running using this ServiceAccount.</em></span>
    <span style="color:#999988"><em>// More info: https://kubernetes.io/docs/concepts/configuration/secret</em></span>
    Secrets []ObjectReference

    ImagePullSecrets []LocalObjectReference

    <span style="color:#999988"><em>// whether pods running as this service account should have an API token automatically mounted.</em></span>
    AutomountServiceAccountToken <span style="color:#000000"><strong>*</strong></span><span style="color:#445588"><strong>bool</strong></span>
}
</code></span></span></span>
<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/staging/src/k8s.io/api/core/v1/types.go#L6005</em></span>

<span style="color:#999988"><em>// Secret holds secret data of a certain type. The total bytes of the values in</em></span>
<span style="color:#999988"><em>// the Data field must be less than MaxSecretSize bytes.</em></span>
<span style="color:#000000"><strong>type</strong></span> Secret <span style="color:#000000"><strong>struct</strong></span> {
    metav1<span style="color:#000000"><strong>.</strong></span>TypeMeta
    metav1<span style="color:#000000"><strong>.</strong></span>ObjectMeta

    Immutable <span style="color:#000000"><strong>*</strong></span><span style="color:#445588"><strong>bool</strong></span>

    Data <span style="color:#000000"><strong>map</strong></span>[<span style="color:#445588"><strong>string</strong></span>][]<span style="color:#445588"><strong>byte</strong></span> <span style="color:#999988"><em>// secret data, base64 encoded</em></span>

    <span style="color:#999988"><em>// stringData allows specifying non-binary secret data in string form.</em></span>
    <span style="color:#999988"><em>// It is provided as a write-only input field for convenience.</em></span>
    StringData <span style="color:#000000"><strong>map</strong></span>[<span style="color:#445588"><strong>string</strong></span>]<span style="color:#445588"><strong>string</strong></span>

    <span style="color:#999988"><em>// Used to facilitate programmatic handling of secret data.</em></span>
    <span style="color:#999988"><em>// More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types</em></span>
    Type SecretType
}
</code></span></span></span>

Token 控制器:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/pkg/controller/serviceaccount/tokens_controller.go#L134</em></span>

<span style="color:#999988"><em>// TokensController manages ServiceAccountToken secrets for ServiceAccount objects</em></span>
<span style="color:#000000"><strong>type</strong></span> TokensController <span style="color:#000000"><strong>struct</strong></span> {
   <span style="color:#000000"><strong>...</strong></span>
}
</code></span></span></span>

4 使用 Kubernetes AuthN 进行测试

在本节中,让我们看看如何将各种 AuthN 策略与 command 结合使用 Line 工具,如 AND Programming (Golang)。 此处的示例非常简单,但它将揭示工作流程和实施细节 我们在上一节中已经介绍过。curl

4.1 准备工作

首先,您应该有一个正在运行的 Kubernetes 集群,例如使用 minikube,但这超出了本文的范围。

通过使用 假令牌kube-apiserver

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span><span style="color:#0086b3">export </span><span style="color:#008080">API_SERVER_URL</span><span style="color:#000000"><strong>=</strong></span><addr> <span style="color:#999988"><em># e.g. https://127.0.0.1:6443</em></span>

<span style="color:#008080">$ </span>curl <span style="color:#000080">-k</span> <span style="color:#000080">-H</span> <span style="color:#dd1144">"Authorization: Bearer 1234"</span> <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods
<span style="color:#000000"><strong>{</strong></span>
  ...
  <span style="color:#dd1144">"message"</span>: <span style="color:#dd1144">"Unauthorized"</span>,
  <span style="color:#dd1144">"code"</span>: 401
<span style="color:#000000"><strong>}</strong></span>
</code></span></span></span>

收到“401 未授权”响应,符合预期。

4.1 静态令牌

本节介绍如何使用静态 Token AuthN 策略从集群外部访问 Kubernetes API。

4.1.1 准备 Token 文件

准备静态 Token 鉴权文件:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span><span style="color:#0086b3">cat</span> /etc/kubernetes/static-tokens.csv
alice-rand1,alice,111,666 <span style="color:#999988"><em># <token>,<user>,<uid>,<gid>, where uid/gid are arbitrary strings</em></span>
bob-rand2,bob,222,666
cindy-rand3,cindy,333,777
</code></span></span></span>

其中,用户和用户各有三个令牌,属于同一用户组。alicebob666

4.1.2 配置kube-apiserver

配置并重新启动它, 例如,如果您的集群是使用二进制文件部署的:kube-apiserver--token-auth-file=/etc/kubernetes/static-tokens.csv

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>systemctl daemon-reload
<span style="color:#008080">$ </span>systemctl restart kube-apiserver
</code></span></span></span>

4.1.3 命令行示例curl

验证 AuthN 正常(但 AuthZ 将失败)

现在再次测试:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>curl <span style="color:#000080">-k</span> <span style="color:#000080">-H</span> <span style="color:#dd1144">"Authorization: Bearer alice-rand1"</span> <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods
<span style="color:#000000"><strong>{</strong></span>
  ...
  <span style="color:#dd1144">"message"</span>: <span style="color:#dd1144">"pods is forbidden: User </span><span style="color:#dd1144">\"</span><span style="color:#dd1144">alice</span><span style="color:#dd1144">\"</span><span style="color:#dd1144"> cannot list resource </span><span style="color:#dd1144">\"</span><span style="color:#dd1144">pods</span><span style="color:#dd1144">\"</span><span style="color:#dd1144"> in API group </span><span style="color:#dd1144">\"\"</span><span style="color:#dd1144"> in the namespace </span><span style="color:#dd1144">\"</span><span style="color:#dd1144">default</span><span style="color:#dd1144">\"</span><span style="color:#dd1144">"</span>,
  <span style="color:#dd1144">"reason"</span>: <span style="color:#dd1144">"Forbidden"</span>,
  <span style="color:#dd1144">"code"</span>: 403
<span style="color:#000000"><strong>}</strong></span>
</code></span></span></span>

嗯,仍然失败,但请注意,现在的响应是 的前一个 ,这表明我们已经 成功进行身份验证,并且请求失败只是因为用户没有足够的权限来执行 操作 - 被 AuthZ 拒绝403 Forbidden401 Unauthorizedalice

添加并传递 AuthZRoleRoleBinding

作为快速补救措施,我们可以创建一个具有适当权限的角色,并且 然后将用户绑定到此角色 [6]。

角色规范 role-pod-reader.yaml

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">apiVersion</span>: <span style="color:#dd1144">rbac.authorization.k8s.io/v1</span>
<span style="color:#008080">kind</span>: <span style="color:#dd1144">Role</span>
<span style="color:#008080">metadata</span>:
  <span style="color:#008080">namespace</span>: <span style="color:#dd1144">default</span>
  <span style="color:#008080">name</span>: <span style="color:#dd1144">pod-reader</span>
<span style="color:#008080">rules</span>:
- <span style="color:#008080">apiGroups</span>: [<span style="color:#dd1144">"</span><span style="color:#dd1144">"</span>] <span style="color:#999988"><em># "" indicates the core API group</em></span>
  <span style="color:#008080">resources</span>: [<span style="color:#dd1144">"</span><span style="color:#dd1144">pods"</span>]
  <span style="color:#008080">verbs</span>: [<span style="color:#dd1144">"</span><span style="color:#dd1144">get"</span>, <span style="color:#dd1144">"</span><span style="color:#dd1144">list"</span>]
</code></span></span></span>

角色绑定规范 rolebinding-for-alice.yaml

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">apiVersion</span>: <span style="color:#dd1144">rbac.authorization.k8s.io/v1</span>
<span style="color:#008080">kind</span>: <span style="color:#dd1144">RoleBinding</span>
<span style="color:#008080">metadata</span>:
  <span style="color:#008080">name</span>: <span style="color:#dd1144">read-pods</span>
  <span style="color:#008080">namespace</span>: <span style="color:#dd1144">default</span>
<span style="color:#008080">subjects</span>:
- <span style="color:#008080">kind</span>: <span style="color:#dd1144">User</span>
  <span style="color:#008080">name</span>: <span style="color:#dd1144">alice</span> <span style="color:#999988"><em># "name" is case sensitive</em></span>
  <span style="color:#008080">apiGroup</span>: <span style="color:#dd1144">rbac.authorization.k8s.io</span>
<span style="color:#008080">roleRef</span>:
  <span style="color:#008080">kind</span>: <span style="color:#dd1144">Role</span>
  <span style="color:#008080">name</span>: <span style="color:#dd1144">pod-reader</span>
  <span style="color:#008080">apiGroup</span>: <span style="color:#dd1144">rbac.authorization.k8s.io</span>
</code></span></span></span>

现在创建它们:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>k apply <span style="color:#000080">-f</span> role-pod-reader.yaml
<span style="color:#008080">$ </span>k apply <span style="color:#000080">-f</span> rolebinding-for-alice.yaml
</code></span></span></span>

然后再次测试:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>curl <span style="color:#000080">-k</span> <span style="color:#000080">-H</span> <span style="color:#dd1144">"Authorization: Bearer alice-rand1"</span> <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods
<span style="color:#000000"><strong>{</strong></span>
  <span style="color:#dd1144">"kind"</span>: <span style="color:#dd1144">"PodList"</span>,
  <span style="color:#dd1144">"apiVersion"</span>: <span style="color:#dd1144">"v1"</span>,
  <span style="color:#dd1144">"items"</span>: <span style="color:#000000"><strong>[</strong></span> ... <span style="color:#000000"><strong>]</strong></span>
<span style="color:#000000"><strong>}</strong></span>
</code></span></span></span>

好的,我们成功得到了我们想要的回复

而且我们可以再次确认,其他两个用户仍然会遇到 403,因为他们没有绑定到角色(为简洁起见,我们只打印状态码):

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>curl <span style="color:#000080">-s</span> <span style="color:#000080">-k</span> <span style="color:#000080">-H</span> <span style="color:#dd1144">"Authorization: Bearer bob-rand2"</span> <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods | jq <span style="color:#dd1144">'.code'</span>
403

<span style="color:#008080">$ </span>curl <span style="color:#000080">-s</span> <span style="color:#000080">-k</span> <span style="color:#000080">-H</span> <span style="color:#dd1144">"Authorization: Bearer cindy-rand3"</span> <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods | jq <span style="color:#dd1144">'.code'</span>
403
</code></span></span></span>

测试完成,现在删除角色绑定(但保留角色 对于后续测试,因此不要执行 ):k delete -f role-pod-reader.yaml

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>k delete <span style="color:#000080">-f</span> rolebinding-for-alice.yaml
</code></span></span></span>

作为另一个测试,让 bind user group 到 role,这样 和 都可以访问资源。666pod-readeralicebob

角色绑定规范 rolebinding-for-group.yaml

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">apiVersion</span>: <span style="color:#dd1144">rbac.authorization.k8s.io/v1</span>
<span style="color:#008080">kind</span>: <span style="color:#dd1144">RoleBinding</span>
<span style="color:#008080">metadata</span>:
  <span style="color:#008080">name</span>: <span style="color:#dd1144">read-pods</span>
  <span style="color:#008080">namespace</span>: <span style="color:#dd1144">default</span>
<span style="color:#008080">subjects</span>:
- <span style="color:#008080">kind</span>: <span style="color:#dd1144">Group</span>
  <span style="color:#008080">name</span>: <span style="color:#dd1144">"</span><span style="color:#dd1144">666"</span>
  <span style="color:#008080">apiGroup</span>: <span style="color:#dd1144">rbac.authorization.k8s.io</span>
<span style="color:#008080">roleRef</span>:
  <span style="color:#008080">kind</span>: <span style="color:#dd1144">Role</span>
  <span style="color:#008080">name</span>: <span style="color:#dd1144">pod-reader</span>
  <span style="color:#008080">apiGroup</span>: <span style="color:#dd1144">rbac.authorization.k8s.io</span>
</code></span></span></span>

应用它:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>k apply <span style="color:#000080">-f</span> rolebinding-for-group.yaml
</code></span></span></span>

并再次测试:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>curl <span style="color:#000080">-k</span> <span style="color:#000080">-H</span> <span style="color:#dd1144">"Authorization: Bearer alice-rand1"</span> <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods
<span style="color:#000000"><strong>{</strong></span>
  <span style="color:#dd1144">"kind"</span>: <span style="color:#dd1144">"PodList"</span>,
  <span style="color:#dd1144">"apiVersion"</span>: <span style="color:#dd1144">"v1"</span>,
  <span style="color:#dd1144">"items"</span>: <span style="color:#000000"><strong>[</strong></span> ... <span style="color:#000000"><strong>]</strong></span>
<span style="color:#000000"><strong>}</strong></span>

<span style="color:#008080">$ </span>curl <span style="color:#000080">-k</span> <span style="color:#000080">-H</span> <span style="color:#dd1144">"Authorization: Bearer bob-rand2"</span> <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods
<span style="color:#000000"><strong>{</strong></span>
  <span style="color:#dd1144">"kind"</span>: <span style="color:#dd1144">"PodList"</span>,
  <span style="color:#dd1144">"apiVersion"</span>: <span style="color:#dd1144">"v1"</span>,
  <span style="color:#dd1144">"items"</span>: <span style="color:#000000"><strong>[</strong></span> ... <span style="color:#000000"><strong>]</strong></span>
<span style="color:#000000"><strong>}</strong></span>

<span style="color:#999988"><em># cindy will still get 403 as she is not in the "666" user group</em></span>
<span style="color:#008080">$ </span>curl <span style="color:#000080">-s</span> <span style="color:#000080">-k</span> <span style="color:#000080">-H</span> <span style="color:#dd1144">"Authorization: Bearer cindy-rand3"</span> <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods | jq <span style="color:#dd1144">'.code'</span>
403
</code></span></span></span>

果然如此!

4.1.4 使用 golang 编程示例

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>package</strong></span> main

<span style="color:#000000"><strong>import</strong></span> (
	<span style="color:#dd1144">"crypto/tls"</span>
	<span style="color:#dd1144">"fmt"</span>
	<span style="color:#dd1144">"io/ioutil"</span>
	<span style="color:#dd1144">"net/http"</span>
)

<span style="color:#000000"><strong>func</strong></span> main() {
	<span style="color:#999988"><em>// Ignore TLS verification (irrelevant to our case) for simple tests</em></span>
	transport <span style="color:#000000"><strong>:=</strong></span> <span style="color:#000000"><strong>&</strong></span>http<span style="color:#000000"><strong>.</strong></span>Transport{TLSClientConfig<span style="color:#000000"><strong>:</strong></span> <span style="color:#000000"><strong>&</strong></span>tls<span style="color:#000000"><strong>.</strong></span>Config{InsecureSkipVerify<span style="color:#000000"><strong>:</strong></span> <span style="color:#008080">true</span>}}
	client <span style="color:#000000"><strong>:=</strong></span> <span style="color:#000000"><strong>&</strong></span>http<span style="color:#000000"><strong>.</strong></span>Client{Transport<span style="color:#000000"><strong>:</strong></span> transport}

	url <span style="color:#000000"><strong>:=</strong></span> <span style="color:#dd1144">"https://127.0.0.1:6443/api/v1/namespaces/default/pods"</span>
	userToken <span style="color:#000000"><strong>:=</strong></span> <span style="color:#dd1144">"alice-rand1"</span>

	req, err <span style="color:#000000"><strong>:=</strong></span> http<span style="color:#000000"><strong>.</strong></span>NewRequest(<span style="color:#dd1144">"GET"</span>, url, <span style="color:#008080">nil</span>)
	req<span style="color:#000000"><strong>.</strong></span>Header<span style="color:#000000"><strong>.</strong></span>Add(<span style="color:#dd1144">"Authorization"</span>, <span style="color:#dd1144">"Bearer "</span><span style="color:#000000"><strong>+</strong></span>userToken)

	resp, err <span style="color:#000000"><strong>:=</strong></span> client<span style="color:#000000"><strong>.</strong></span>Do(req)
	<span style="color:#000000"><strong>if</strong></span> err <span style="color:#000000"><strong>!=</strong></span> <span style="color:#008080">nil</span> {
		fmt<span style="color:#000000"><strong>.</strong></span>Println(<span style="color:#dd1144">"HTTP request failed: "</span>, err)
	}
	<span style="color:#000000"><strong>defer</strong></span> resp<span style="color:#000000"><strong>.</strong></span>Body<span style="color:#000000"><strong>.</strong></span>Close()

	<span style="color:#000000"><strong>if</strong></span> body, err <span style="color:#000000"><strong>:=</strong></span> ioutil<span style="color:#000000"><strong>.</strong></span>ReadAll(resp<span style="color:#000000"><strong>.</strong></span>Body); err <span style="color:#000000"><strong>!=</strong></span> <span style="color:#008080">nil</span> {
		fmt<span style="color:#000000"><strong>.</strong></span>Println(<span style="color:#dd1144">"Error while reading response:"</span>, err)
	} <span style="color:#000000"><strong>else</strong></span> {
		fmt<span style="color:#000000"><strong>.</strong></span>Println(<span style="color:#445588"><strong>string</strong></span>([]<span style="color:#445588"><strong>byte</strong></span>(body)))
	}
}
</code></span></span></span>

配置适当的角色绑定(否则将收到上述 401/403 响应):

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>go run static-token-client.go
<span style="color:#000000"><strong>{</strong></span><span style="color:#dd1144">"kind"</span>:<span style="color:#dd1144">"PodList"</span>,<span style="color:#dd1144">"apiVersion"</span>:<span style="color:#dd1144">"v1"</span>,<span style="color:#dd1144">"items"</span>:[...]<span style="color:#000000"><strong>}</strong></span>
</code></span></span></span>

4.2 X509 证书

4.2.1 准备根 CA

检查您是否已经配置,如果没有,您应该生成 CA 文件。kube-apiserver--client-ca-file=xx

命令改编自配置 CA 和生成 TLS 证书

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span><span style="color:#0086b3">cat</span> <span style="color:#000000"><strong>></strong></span> ca-config.json <span style="color:#000000"><strong><<</strong></span><span style="color:#008080">EOF</span><span style="color:#dd1144">
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "kubernetes": {
        "usages": ["signing", "key encipherment", "server auth", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
</span><span style="color:#008080">EOF

</span><span style="color:#008080">$ </span><span style="color:#0086b3">cat</span> <span style="color:#000000"><strong>></strong></span> ca-csr.json <span style="color:#000000"><strong><<</strong></span><span style="color:#008080">EOF</span><span style="color:#dd1144">
{
  "CN": "Kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "CA",
      "ST": "Oregon"
    }
  ]
}
</span><span style="color:#008080">EOF

</span><span style="color:#008080">$ </span>cfssl gencert <span style="color:#000080">-initca</span> ca-csr.json | cfssljson <span style="color:#000080">-bare</span> ca
</code></span></span></span>

结果将是:

  1. ca.pem:CA 证书
  2. ca-key.pem:CA 私钥

4.2.2 配置kube-apiserver

启动并重新启动它。kube-apiserver--client-ca-file=/var/lib/kubernetes/ca.pem

4.2.3 为用户颁发 X509 客户端证书

假设我们想为用户颁发证书:dylan

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span><span style="color:#0086b3">cat </span>dylan-csr.json
<span style="color:#000000"><strong>{</strong></span>
  <span style="color:#dd1144">"CN"</span>: <span style="color:#dd1144">"dylan"</span>,
  <span style="color:#dd1144">"key"</span>: <span style="color:#000000"><strong>{</strong></span>
    <span style="color:#dd1144">"algo"</span>: <span style="color:#dd1144">"rsa"</span>,
    <span style="color:#dd1144">"size"</span>: 2048
  <span style="color:#000000"><strong>}</strong></span>,
  <span style="color:#dd1144">"names"</span>: <span style="color:#000000"><strong>[</strong></span>
    <span style="color:#000000"><strong>{</strong></span>
      <span style="color:#dd1144">"C"</span>: <span style="color:#dd1144">"US"</span>,
      <span style="color:#dd1144">"L"</span>: <span style="color:#dd1144">"Portland"</span>,
      <span style="color:#dd1144">"O"</span>: <span style="color:#dd1144">"usergroup1"</span>,
      <span style="color:#dd1144">"OU"</span>: <span style="color:#dd1144">"Cracking Kubernetes AuthN Model"</span>,
      <span style="color:#dd1144">"ST"</span>: <span style="color:#dd1144">"Oregon"</span>
    <span style="color:#000000"><strong>}</strong></span>
  <span style="color:#000000"><strong>]</strong></span>
<span style="color:#000000"><strong>}</strong></span>
</code></span></span></span>

对于我们简单的测试用例,我们只需要注意以下字段:

  1. "CN": 用户名
  2. "O": 用户组 (组织)

现在生成证书:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>cfssl gencert <span style="color:#000080">-ca</span><span style="color:#000000"><strong>=</strong></span>ca.pem <span style="color:#000080">-ca-key</span><span style="color:#000000"><strong>=</strong></span>ca-key.pem <span style="color:#000080">-config</span><span style="color:#000000"><strong>=</strong></span>ca-config.json <span style="color:#000080">-profile</span><span style="color:#000000"><strong>=</strong></span>kubernetes dylan-csr.json | cfssljson <span style="color:#000080">-bare</span> dylan
<span style="color:#000000"><strong>[</strong></span>INFO] generate received request
<span style="color:#000000"><strong>[</strong></span>INFO] received CSR
<span style="color:#000000"><strong>[</strong></span>INFO] generating key: rsa-2048
<span style="color:#000000"><strong>[</strong></span>INFO] encoded CSR
<span style="color:#000000"><strong>[</strong></span>INFO] signed certificate with serial number 14406...

<span style="color:#008080">$ </span><span style="color:#0086b3">ls </span>dylan<span style="color:#000000"><strong>*</strong></span>.pem
dylan-key.pem  dylan.pem <span style="color:#999988"><em># <-- private key and certificate</em></span>
</code></span></span></span>

4.2.4 命令行示例curl

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>curl <span style="color:#000080">--key</span> dylan-key.pem <span style="color:#000080">--cert</span> dylan.pem <span style="color:#000080">--cacert</span> ca.pem <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods
<span style="color:#000000"><strong>{</strong></span>
  ...
  <span style="color:#dd1144">"message"</span>: <span style="color:#dd1144">"pods is forbidden: User </span><span style="color:#dd1144">\"</span><span style="color:#dd1144">dylan</span><span style="color:#dd1144">\"</span><span style="color:#dd1144"> cannot list resource </span><span style="color:#dd1144">\"</span><span style="color:#dd1144">pods</span><span style="color:#dd1144">\"</span><span style="color:#dd1144"> in API group </span><span style="color:#dd1144">\"\"</span><span style="color:#dd1144"> in the namespace </span><span style="color:#dd1144">\"</span><span style="color:#dd1144">default</span><span style="color:#dd1144">\"</span><span style="color:#dd1144">"</span>,
  <span style="color:#dd1144">"reason"</span>: <span style="color:#dd1144">"Forbidden"</span>,
  <span style="color:#dd1144">"code"</span>: 403
<span style="color:#000000"><strong>}</strong></span>
</code></span></span></span>

正如预期的那样,消息指示已成功 authenticated,但不允许访问 API,因为我们尚未通过 AuthZ 获得权限。 现在为 创建角色绑定:403dylandylan

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">apiVersion</span>: <span style="color:#dd1144">rbac.authorization.k8s.io/v1</span>
<span style="color:#008080">kind</span>: <span style="color:#dd1144">RoleBinding</span>
<span style="color:#008080">metadata</span>:
  <span style="color:#008080">name</span>: <span style="color:#dd1144">read-pods</span>
  <span style="color:#008080">namespace</span>: <span style="color:#dd1144">default</span>
<span style="color:#008080">subjects</span>:
- <span style="color:#008080">kind</span>: <span style="color:#dd1144">User</span>
  <span style="color:#008080">name</span>: <span style="color:#dd1144">dylan</span>
  <span style="color:#008080">apiGroup</span>: <span style="color:#dd1144">rbac.authorization.k8s.io</span>
<span style="color:#008080">roleRef</span>:
  <span style="color:#008080">kind</span>: <span style="color:#dd1144">Role</span>
  <span style="color:#008080">name</span>: <span style="color:#dd1144">pod-reader</span>
  <span style="color:#008080">apiGroup</span>: <span style="color:#dd1144">rbac.authorization.k8s.io</span>
</code></span></span></span>
<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>k apply <span style="color:#000080">-f</span> rolebinding-for-dylan.yaml
rolebinding.rbac.authorization.k8s.io/read-pods configured
</code></span></span></span>

再次测试:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>curl <span style="color:#000080">--key</span> dylan-key.pem <span style="color:#000080">--cert</span> dylan.pem <span style="color:#000080">--cacert</span> ca.pem <span style="color:#008080">$API_SERVER_URL</span>/api/v1/namespaces/default/pods
<span style="color:#000000"><strong>{</strong></span>
  <span style="color:#dd1144">"kind"</span>: <span style="color:#dd1144">"PodList"</span>,
  <span style="color:#dd1144">"apiVersion"</span>: <span style="color:#dd1144">"v1"</span>,
  <span style="color:#dd1144">"items"</span>: <span style="color:#000000"><strong>[</strong></span> ... <span style="color:#000000"><strong>]</strong></span>
<span style="color:#000000"><strong>}</strong></span>
</code></span></span></span>

成功的!

4.2.5 使用 golang 编程示例

代码片段改编自 michaljemala/tls-client.go

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>package</strong></span> main

<span style="color:#000000"><strong>import</strong></span> (
	<span style="color:#dd1144">"crypto/tls"</span>
	<span style="color:#dd1144">"crypto/x509"</span>
	<span style="color:#dd1144">"flag"</span>
	<span style="color:#dd1144">"io/ioutil"</span>
	<span style="color:#dd1144">"log"</span>
	<span style="color:#dd1144">"net/http"</span>
)

<span style="color:#000000"><strong>var</strong></span> (
	certFile <span style="color:#000000"><strong>=</strong></span> flag<span style="color:#000000"><strong>.</strong></span>String(<span style="color:#dd1144">"cert"</span>, <span style="color:#dd1144">"someCertFile"</span>, <span style="color:#dd1144">"A PEM eoncoded certificate file."</span>)
	keyFile  <span style="color:#000000"><strong>=</strong></span> flag<span style="color:#000000"><strong>.</strong></span>String(<span style="color:#dd1144">"key"</span>, <span style="color:#dd1144">"someKeyFile"</span>, <span style="color:#dd1144">"A PEM encoded private key file."</span>)
	caFile   <span style="color:#000000"><strong>=</strong></span> flag<span style="color:#000000"><strong>.</strong></span>String(<span style="color:#dd1144">"CA"</span>, <span style="color:#dd1144">"someCertCAFile"</span>, <span style="color:#dd1144">"A PEM eoncoded CA's certificate file."</span>)
)

<span style="color:#000000"><strong>func</strong></span> main() {
	flag<span style="color:#000000"><strong>.</strong></span>Parse()

	<span style="color:#999988"><em>// Load client cert</em></span>
	cert, err <span style="color:#000000"><strong>:=</strong></span> tls<span style="color:#000000"><strong>.</strong></span>LoadX509KeyPair(<span style="color:#000000"><strong>*</strong></span>certFile, <span style="color:#000000"><strong>*</strong></span>keyFile)
	<span style="color:#000000"><strong>if</strong></span> err <span style="color:#000000"><strong>!=</strong></span> <span style="color:#008080">nil</span> {
		log<span style="color:#000000"><strong>.</strong></span>Fatal(err)
	}

	<span style="color:#999988"><em>// Load CA cert</em></span>
	caCert, err <span style="color:#000000"><strong>:=</strong></span> ioutil<span style="color:#000000"><strong>.</strong></span>ReadFile(<span style="color:#000000"><strong>*</strong></span>caFile)
	<span style="color:#000000"><strong>if</strong></span> err <span style="color:#000000"><strong>!=</strong></span> <span style="color:#008080">nil</span> {
		log<span style="color:#000000"><strong>.</strong></span>Fatal(err)
	}
	caCertPool <span style="color:#000000"><strong>:=</strong></span> x509<span style="color:#000000"><strong>.</strong></span>NewCertPool()
	caCertPool<span style="color:#000000"><strong>.</strong></span>AppendCertsFromPEM(caCert)

	<span style="color:#999988"><em>// Setup HTTPS client</em></span>
	tlsConfig <span style="color:#000000"><strong>:=</strong></span> <span style="color:#000000"><strong>&</strong></span>tls<span style="color:#000000"><strong>.</strong></span>Config{
		Certificates<span style="color:#000000"><strong>:</strong></span> []tls<span style="color:#000000"><strong>.</strong></span>Certificate{cert},
		RootCAs<span style="color:#000000"><strong>:</strong></span>      caCertPool,
	}
	tlsConfig<span style="color:#000000"><strong>.</strong></span>BuildNameToCertificate()
	transport <span style="color:#000000"><strong>:=</strong></span> <span style="color:#000000"><strong>&</strong></span>http<span style="color:#000000"><strong>.</strong></span>Transport{TLSClientConfig<span style="color:#000000"><strong>:</strong></span> tlsConfig}
	client <span style="color:#000000"><strong>:=</strong></span> <span style="color:#000000"><strong>&</strong></span>http<span style="color:#000000"><strong>.</strong></span>Client{Transport<span style="color:#000000"><strong>:</strong></span> transport}

	<span style="color:#999988"><em>// Do GET something</em></span>
	resp, err <span style="color:#000000"><strong>:=</strong></span> client<span style="color:#000000"><strong>.</strong></span>Get(<span style="color:#dd1144">"https://127.0.0.1:6443/api/v1/namespaces/default/pods"</span>)
	<span style="color:#000000"><strong>if</strong></span> err <span style="color:#000000"><strong>!=</strong></span> <span style="color:#008080">nil</span> {
		log<span style="color:#000000"><strong>.</strong></span>Fatal(err)
	}
	<span style="color:#000000"><strong>defer</strong></span> resp<span style="color:#000000"><strong>.</strong></span>Body<span style="color:#000000"><strong>.</strong></span>Close()

	<span style="color:#999988"><em>// Dump response</em></span>
	data, err <span style="color:#000000"><strong>:=</strong></span> ioutil<span style="color:#000000"><strong>.</strong></span>ReadAll(resp<span style="color:#000000"><strong>.</strong></span>Body)
	<span style="color:#000000"><strong>if</strong></span> err <span style="color:#000000"><strong>!=</strong></span> <span style="color:#008080">nil</span> {
		log<span style="color:#000000"><strong>.</strong></span>Fatal(err)
	}
	log<span style="color:#000000"><strong>.</strong></span>Println(<span style="color:#445588"><strong>string</strong></span>(data))
}
</code></span></span></span>

配置适当的角色绑定后:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>go run x509-client.go <span style="color:#000080">-cert</span> dylan.pem <span style="color:#000080">-key</span> dylan-key.pem <span style="color:#000080">-CA</span> ca.pem
<span style="color:#000000"><strong>{</strong></span><span style="color:#dd1144">"kind"</span>:<span style="color:#dd1144">"PodList"</span>,<span style="color:#dd1144">"apiVersion"</span>:<span style="color:#dd1144">"v1"</span>,<span style="color:#dd1144">"items"</span>:[...]<span style="color:#000000"><strong>}</strong></span>
</code></span></span></span>

4.3 服务账户

对于 ,将为每个 serviceaccount 自动创建一个密钥/令牌:< v1.24.0

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>k create sa my-sa
serviceaccount/my-sa created

<span style="color:#008080">$ </span>k get sa
NAME      SECRETS   AGE
my-sa     1         2s  <span style="color:#999988"><em># <-- A secret is created for this SA</em></span>

<span style="color:#008080">$ </span>k get sa my-sa <span style="color:#000080">-o</span> yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: <span style="color:#dd1144">"2022-06-08T12:59:15Z"</span>
  name: my-sa
  namespace: default
  resourceVersion: <span style="color:#dd1144">"7682925"</span>
  uid: a0a2738e-5ad8-4f07-8588-10c82caac02e
secrets:
- name: my-sa-token-5dx8g <span style="color:#999988"><em># <-- A secret is created for this SA</em></span>

<span style="color:#008080">$ </span>k get secrets my-sa-token-5dx8g <span style="color:#000080">-o</span> yaml
apiVersion: v1
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FUR...FURS0tLS0tCg<span style="color:#000000"><strong>==</strong></span>
  namespace: <span style="color:#008080">ZGVmYXVsdA</span><span style="color:#000000"><strong>==</strong></span>
  token: ZXlKaGJHY2lPaUpT...UnYxbm9wbjAtUQ<span style="color:#000000"><strong>==</strong></span>               <span style="color:#999988"><em># <--- The token in the secret</em></span>
kind: Secret
<span style="color:#0086b3">type</span>: kubernetes.io/service-account-token

<span style="color:#008080">$ </span>k get secrets my-sa-token-5dx8g <span style="color:#000080">-o</span> <span style="color:#008080">jsonpath</span><span style="color:#000000"><strong>=</strong></span><span style="color:#dd1144">'{.data.token}'</span> | <span style="color:#0086b3">base64</span> <span style="color:#000080">-d</span>
eyJhb...ZYT-VTp-v-rx8Rv1nopn0-Q
</code></span></span></span>

将此令牌放入 Authorization: Bearer {token} 并发送请求。

或者,你也可以使用 kubeconfig + token 文件,例如配置 Cilium:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>cilium-agent <span style="color:#000080">--k8s-kubeconfig-path</span><span style="color:#000000"><strong>=</strong></span>/etc/cilium/cilium.kubeconfig ...
</code></span></span></span>

哪里

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span><span style="color:#0086b3">cat</span> /etc/cilium/cilium.kubeconfig
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /etc/cilium/ca.crt
    server: https://<addr>:443
  name: default-cluster
contexts:
- context:
    cluster: default-cluster
    user: default-user
  name: default-context
current-context: default-context
kind: Config
preferences: <span style="color:#000000"><strong>{}</strong></span>
<span style="color:#0086b3">users</span>:
- name: default-user
  user:
    tokenFile: /etc/cilium/cilium.token

<span style="color:#008080">$ </span><span style="color:#0086b3">cat</span> /etc/cilium/cilium.token
eyJhbG...xuOMjQ
</code></span></span></span>

实现自定义 Kubernetes 身份验证方法 [4] 中提供了一个很好的示例。

为:>= v1.24.0

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>k create sa my-sa
serviceaccount/my-sa created

<span style="color:#008080">$ </span>k get sa
NAME      SECRETS   AGE
my-sa     0         2s    <span style="color:#999988"><em># <-- No secrets created for this SA</em></span>

<span style="color:#008080">$ </span>k get sa my-sa <span style="color:#000080">-o</span> yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: <span style="color:#dd1144">"2022-06-08T13:48:02Z"</span>
  name: my-sa
  namespace: default
  resourceVersion: <span style="color:#dd1144">"7686160"</span>
  uid: baa8efd9-6219-46d3-9efd-87d6509dfd32

<span style="color:#008080">$ </span>k create token my-sa   <span style="color:#999988"><em># Create a token for serviceaccount 'my-sa' by our own</em></span>
eyJhbGciO...yJbkfZQJt7Q  <span style="color:#999988"><em># Note that this token is not base64 encoded</em></span>
</code></span></span></span>

4.4 LDAP 或 OIDC

实现自定义 Kubernetes 身份验证方法 [5] 中提供了一个深入的示例。

5 讨论

一些深入的讨论。

5.1 更多 AuthN 策略

Kubernetes 支持以下策略,我们在前面的部分中没有提到或详细介绍:

  1. 请求标头

    选择:

    • --requestheader-client-ca-file=kube-apiserver
  2. 引导令牌 (v1.18+)

    允许简化新集群的引导。 这些令牌作为 Secret 存储在 kube-system 中 命名空间,可在其中动态管理和创建它们。

    选择:

    • --enable-bootstrap-token-authkube-apiserver;
    • --controllers=<others>,tokerncleaner在 : 上启用 TokenCleaner 控制器。kube-controller-manager

    验证器验证为 。它包括 在组中。system:bootstrap:<Token ID>system:bootstrappers

  3. OpenID Connect 令牌

    OpenID Connect 是某些 OAuth2 提供商支持的 OAuth2 的一种形式, 例如 Azure Active Directory (AD)、Salesforce 和 Google。该协议对 OAuth2 的主要扩展还返回了 JWT(JSON Web 令牌)格式 称为 ID Token,它包括由服务器签名的著名用户电子邮件。

    为了识别用户,验证器使用 id_token(而不是 access_token)作为不记名令牌。

    选择:

    • --oidc-* on kube-apiserver

    使用令牌:kubectl --token=eyJhb...x01_yD35-rw get nodes

  4. Webhook 令牌身份验证

    Webhook 身份验证是用于验证不记名令牌的钩子

    选择:

    • --authentication-token-webhook-config-file描述如何访问远程 Webhook 服务的文件。
    • --authentication-token-webhook-cache-ttl缓存身份验证决策的时间。默认为 2 分钟。
    • --authentication-token-webhook-version=v1例如 TokenReview 对象从 Webhook 发送/接收信息。authentication.k8s.io/v1

    当客户端尝试使用不记名 令牌,则身份验证 Webhook 会将包含令牌的 JSON 序列化 TokenReview 对象 POST 到远程服务kube-apiserver

  5. 身份验证代理

    选择:

    • --proxy-client-cert-file
    • --proxy-client-key-file

    与其他身份验证协议(LDAP、SAML、Kerberos 、 备用 x509 方案等)可以使用身份验证代理来完成 或身份验证 Webhook。

您可以一次启用多种身份验证方法。您应该至少使用两种方法

  1. 服务账户的服务账户令牌
  2. 至少一种其他用户身份验证方法。

启用多个验证器模块时,第一个 成功验证请求短路评估。 kube-apiserver 不保证身份验证器运行的顺序

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

5.2 AuthN、AuthZ 和准入控制阶段

事实上,Kubernetes 中还有一个准入控制阶段,它可以 用于在将对象持久化到后端之前验证客户端请求 存储 (etcd):

图 6-1.处理客户端请求时的 AuthN、AuthZ 和准入控制 [3]

有关更多信息,请参见 [3]。

5.3 手动创建ServiceAccount

服务帐户持有者令牌对于在集群外部使用以及希望与 Kubernetes API 通信的应用程序完全有效。

用于手动创建和检查服务帐户的命令:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>kubectl create serviceaccount jenkins
serviceaccount <span style="color:#dd1144">"jenkins"</span> created
</code></span></span></span>

检查关联的密钥

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>kubectl get serviceaccounts jenkins <span style="color:#000080">-o</span> yaml
apiVersion: v1
kind: ServiceAccount
...
secrets:
- name: jenkins-token-1yvwg  <span style="color:#999988"><em># <---- the associated secret</em></span>
</code></span></span></span>

创建的密钥包含 的公有 CA 和已签名的 JSON Web 令牌 (JWT)。kube-apiserver

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>kubectl get secret jenkins-token-1yvwg <span style="color:#000080">-o</span> yaml
apiVersion: v1
kind: Secret
<span style="color:#0086b3">type</span>: kubernetes.io/service-account-token
data:
  ca.crt: xxx               <span style="color:#999988"><em># <---- APISERVER'S CA BASE64 ENCODED</em></span>
  namespace: <span style="color:#008080">ZGVmYXVsdA</span><span style="color:#000000"><strong>==</strong></span>
  token: xxx                <span style="color:#999988"><em># <---- BEARER TOKEN BASE64 ENCODED</em></span>
...
</code></span></span></span>

签名的 JWT 可以用作持有者令牌,以验证为给定的 service 帐户。将 base64 编码的令牌解码为不记名令牌:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>k get secrets jenkins-token-1yvwg <span style="color:#000080">-o</span> <span style="color:#008080">jsonpath</span><span style="color:#000000"><strong>={</strong></span>.data.token<span style="color:#000000"><strong>}</strong></span> | <span style="color:#0086b3">base64</span> <span style="color:#000080">-d</span>
eyJhbGciOiJSUzI1NiIsImt...MvMzBl37sNzeA8w
</code></span></span></span>

有关如何在请求中包含令牌的信息,请参阅上文。通常 这些 secret 被挂载到 Pod 中,以便在集群内访问 ,但也可以从集群外部使用。kube-apiserver

5.4 从客户端凭证中提取用户信息

当 HTTP 请求被发送到 时,插件会尝试关联以下内容 属性替换为请求 [1]:kube-apiserver

  • 用户名:字符串,例如 、 .kube-adminjane@example.com
  • UID:一个字符串,尝试比 username 更一致和唯一
  • :例如 、 .system:mastersdevops-team
  • 额外字段:包含其他信息的映射,授权方可能会发现有用。string -> string list
<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/kubernetes/kubernetes/blob/v1.23.1/staging/src/k8s.io/apiserver/pkg/authentication/user/user.go#L20</em></span>

<span style="color:#999988"><em>// Info describes a user that has been authenticated to the system.</em></span>
<span style="color:#000000"><strong>type</strong></span> Info <span style="color:#000000"><strong>interface</strong></span> {
    GetName() <span style="color:#445588"><strong>string</strong></span>
    GetUID() <span style="color:#445588"><strong>string</strong></span>
    GetGroups() []<span style="color:#445588"><strong>string</strong></span>
    GetExtra() <span style="color:#000000"><strong>map</strong></span>[<span style="color:#445588"><strong>string</strong></span>][]<span style="color:#445588"><strong>string</strong></span>
}
</code></span></span></span>

所有值对身份验证系统都是不透明的,并且仅在由授权方解释时才有意义。

5.5 用户模拟

另一个与 AuthN/AuthZ 相关但尚未讨论的主题是用户模拟 [1]。

使用组模拟用户时使用的模拟标头示例:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code>Impersonate-User: jane.doe@example.com
Impersonate-Group: developers
Impersonate-Group: admins
</code></span></span></span>

使用 UID 和 extra 字段模拟用户时使用的模拟标头示例:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code>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
</code></span></span></span>

使用 CLI:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>kubectl drain mynode <span style="color:#000080">--as</span><span style="color:#000000"><strong>=</strong></span>superman <span style="color:#000080">--as-group</span><span style="color:#000000"><strong>=</strong></span>system:masters
node/mynode cordoned
node/mynode drained
</code></span></span></span>

6 总结

这篇文章深入探讨了 Kubernetes AuthN 模块,我们尝试通过以下方式来理解它 自己设计一个。

希望在阅读这篇文章后,读者对 Kubernetes AuthN 模块的工作原理以及如何 以管理员或开发人员的身份正确配置、使用和管理它。

引用

标签:令牌,Kubernetes,用户,身份验证,apiserver,token,AuthN
From: https://blog.csdn.net/hao_wujing/article/details/144955198

相关文章

  • Blazor用户身份验证状态详解
        在Blazor应用程序中,AuthenticationState是一个核心概念,用于表示用户的身份验证状态。它提供有关当前用户的信息,例如是否已登录、用户的身份信息(如用户名、角色等)。AuthenticationState通常由AuthenticationStateProvider提供,Blazor使用它来管理和传播用户......
  • failed to run Kubelet: unable to load bootstrap kubeconfig: stat /etc/kubernetes
    解决方法备份重新生成证书#cd/etc/kubernetes/pki/#mkdirbackup#mvapiserver.crtapiserver-etcd-client.keyapiserver-kubelet-client.crtfront-proxy-ca.crtfront-proxy-client.crtfront-proxy-client.keyfront-proxy-ca.keyapiserver-kubelet-client.keyapi......
  • Kubernetes集群运维生产常见问题解析与解决方案
    前言:在Kubernetes集群的日常运维工作中,我们难免会遇到各种各样的问题。这些问题可能涉及到集群的部署、配置、监控、性能优化等多个方面。为了解决这些问题,我们需要不断地学习和积累经验。在这里,我打算收集并整理一些网友曾经提出的问题,并提供相应的解析和解决方案,之前的问题无从......
  • Kubernetes 提供了多种 Pod 自动扩展的方法
    Kubernetes提供了多种Pod自动扩展的方法,主要包括以下几种:水平自动扩缩容(HPA)基本原理:通过监测Pod的资源使用情况(如CPU、内存等),根据预设的阈值自动增加或减少Pod副本数量。适用场景:适用于负载波动较大且可以通过增加Pod副本来缓解压力的应用。配置示例:yaml复制......
  • Kubernetes 监控实践:基于 Prometheus-Operator 的完整解决方案
    Kubernetes(K8s)的动态性和分布式特性为应用部署带来了极大的便利,同时也使监控变得复杂而繁琐。幸运的是,Prometheus-Operator提供了一种高效的方式,通过抽象Kubernetes的原生资源(CRD)来配置和管理整个监控栈,极大地简化了监控的部署和运维。本文将从实际操作出发,介绍如何通过Prome......
  • Kubernetes 组件中的证书作用与使用方法
    Kubernetes组件中的证书作用与使用方法Kubernetes(K8s)是一个容器编排平台,在云原生架构中广泛应用。在Kubernetes中,证书是保证集群安全通信的关键组成部分。每个组件间的相互通信都依赖于证书来验证身份和加密数据。本文将介绍Kubernetes中各个核心组件的证书作用和使用......
  • CICD Day6、基于kubernetes动态创建代理
    Jenkins支持基于kubernetes动态创建代理,使代理程序能够运行在Pod中,这种方法可以根据构建任务的变化动态的增减代理,充分利用kubernetes的特性,为分布式构建提供灵活的运行环境如下图所示当项目触发构建时,Jenkins会调用kubernetesapi创建一个专用的pod作为从节点,在该pod执行......
  • 零基础手把手教您在笔记本电脑上部署kubernetes 1.31.3 集群
    我是知识的实践者,关注我,手把手教您部署实践。贵在真实,主打就是一个按步骤做下去不会出错。一、Kubernetes概述一句话,他很强大,你必须要学会,否则就跟不上技术的潮流了。本人资源有限,在笔记本电脑上16G内存部署k8s集群,让大家都能接触到新技术NAMESTATUSROLES......
  • Kubernetes 故障排除简单指南
    Kubernetes故障排除简单指南介绍毫无疑问,Kubernetes在部署和管理容器化应用程序的方法上带来了标准转变。然而,无论系统设计得多么好,随着时间的推移,它们都会遇到问题。本指南概述了Kubernetes部署期间最常见的故障源以及解决这些问题的方法组合。这包括资源分配调整、网......
  • kubeadm 快速搭建 Kubernetes 集群
    快速搭建K8s集群角色ipk8s-master-01192.168.111.170k8s-node-01192.168.111.171k8s-node-02192.168.111.172服务器需要连接互联网下载镜像软件版本Docker24.0.0(CE)Kubernetes1.28初始化配置关闭防火墙systemctlstopfirewalld&&......