大家读完觉得有帮助和意义记得关注和点赞!!!
这篇文章深入探讨了 Kubernetes 身份验证 (AuthN) 模型。具体说来 我们将从分析 Kubernetes 中 AuthN 的技术需求开始,然后设计一个 对于它(假设它还没有),最终解决方案有一个端到端的工作流程,如下所示:
希望读者在看完这篇文章后,对 Kubernetes AuthN 模块的工作原理有更深入的了解(带策略 如静态令牌、持有者令牌、X509 证书、ServiceAccounts、OIDC 等),以及 如何以管理员身份正确配置、使用和管理它 开发 人员。
目录
1.1 Kubernetes API 并查看会发生什么curl
2.6.2 Support X509 certificate (for out-of-cluster users/apps)
2.6.3 支持(主要针对集群内应用程序)ServiceAccount
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>
输出提醒我们,服务器正在使用无法识别的 证书(例如自签名),因此阻止了我们潜在的不安全。但 出于测试目的,我们可以关闭证书验证并继续:https
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">-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
关于我们之前的测试用例:
-
AuthN:我们没有提供任何用户凭证,因此 vanilla 身份验证 会失败;但是,取决于 是否允许匿名访问 :
kube-apiserver
- 1.1 不允许:直接返回
401 未授权
(注意此状态 code 是一个长期存在的用词错误,因为它表示身份验证错误而不是授权错误,感谢 [4] 指出这一点); - 1.2 允许:以
system:anonymous
用户身份继续(我们的情况)并进入下一阶段 (AuthZ);
- 1.1 不允许:直接返回
-
AuthZ:检查是否有列出集群中 namespace 的权限,
system:anonymous
- 2.1 否:返回 403 禁止(我们的情况);
- 2.2 是:执行业务处理;
合理而清晰。
事实上,每个请求都应该与一个用户绑定,或者是 视为匿名请求。在这里,请求 可能来自集群内部或外部的进程,也可能来自人类用户 键入 kubectl 或 kubelets 在节点上,或者键入 control plane 的成员。 服务器中的 AuthN 模块将使用它提供的凭证对请求进行身份验证,或者 静态令牌、证书或外部托管的身份。kube-apiserver
AuthN 模块至关重要(而且不可避免地很复杂),因为它是整个系统的第一个守门人。 让我们简要描述一下本模块的要求。
1.3 AuthN 模块的要求
要成为像 Kubernetes 中那样实用的 AuthN 模块,至少必须满足以下属性:
- 支持人工用户和程序用户;
- 支持外部用户(例如部署在 OpenStack 或裸机系统中的应用程序)和内部用户(例如由 Kubernetes 集群本身);
- 支持常见的 AuthN 策略,例如 static token、bearer 令牌、X509 证书、OIDC(我们在此处不包括 BasicAuth,因为它具有 已从 Kubernetes 中删除,它基本上不提供任何 使用加密标准的安全机制);
v1.19
- 支持同时启用 multipel AuthN 策略;
- 可扩展性:易于添加新的 AuthN 策略或逐步淘汰旧的 AuthN 策略;
- (可选)支持匿名访问(就像我们上面看到的情况一样)。
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 集群内部来判断,我们可以 将用户分为以下两种类型:
-
Kubernetes 托管用户:由 Kubernetes 集群本身并由集群内应用程序使用, 我们将它们命名为 “服务账户”;
-
非 Kubernetes 托管用户:Kubernetes 集群外部的用户,例如
- 具有集群管理员提供的静态令牌或证书的用户;
- 用户通过 Keystone、Google 帐户和 LDAP 等外部身份提供商进行身份验证。
此差异的实现含义:
- 前者是原生的 Kubernetes 对象,所以我们需要为它们定义一个规范(数据模型);
- 后者不是 Kubernetes 对象,因此不会有它们的规范。
2.2.1 简介ServiceAccount
根据我们的设计,Servicea 帐户通常会以自动方式为部署在集群中的应用程序创建,使用 通过应用程序 (pod) 访问 .kube-apiserver
kube-apiserver
规范简介:将账户名绑定到 Token 以 Kubernetes 格式存储:ServiceAccount
Secret
<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 工作流程。 收到客户端请求时,
- 从请求标头中提取 AuthN 凭据;
-
根据验证器列表对请求进行身份验证;
- 失败时:返回 “401 Unauthorized”
- 成功时:将用户信息添加到请求上下文中,并从请求头中删除 AuthN 信息;转到下一个处理;
- 如果所有验证器都失败并且启用了匿名访问,请尝试匿名访问;
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 模块将没有信息来做权限判断。 说到这里,这就是我们的简单设计,
-
定义静态 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>
-
添加一个 CLI 选项来加载令牌文件,例如
--token-auth-file=/etc/kubernetes/static-tokens.csv
。kube-apiserver
整个工作流程如下所示:
图 3-1.支持静态令牌 AuthN 策略
- Admin:创建一个 CSV 令牌身份验证文件,提供给 Kubernetes 集群;
- Admin:从这个开始是配置,读取并缓存内存中的信息;
kube-apiserver
- 管理员:将令牌分配给外部用户和/或应用程序;
- 客户端:发送请求时,在 bearer token 字段(
Authorization: Bearer TOKEN)
中携带 token; - 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 策略
- 管理员:准备证书颁发机构 (CA),用于验证客户端证书;
- 管理员:从这个 is config 开始(带有一个新选项
--client-ca-file=FILE
kube-apiserver
); -
管理员:向外部用户和/或应用程序颁发带有根 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):用户的组成员资格。要包含用户的多个组成员资格,请在证书中包含多个组织字段。
- 客户端:发送请求时,将证书放在 TLS 字段中;
- 服务器 ():验证客户端证书 针对根 CA 的 API 进行验证。如果提供并验证了客户端证书,请提取信息。
kube-apiserver
2.6.3 支持(主要针对集群内应用程序)ServiceAccount
以上两种策略需要一个(人工)管理员来生成和 分发客户端凭据,这显然对本机不友好 部署在 Kubernetes 集群中的应用程序。对于后一种情况,有 应该是一种自动方式。这就是我们引入 abstration 的原因。ServiceAccount
对于实现,
- 它还使用已签名的不记名令牌来验证请求;
-
仍然使用 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:serviceaccounts
system: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 策略,
- 支持请求标头身份验证(例如 headers)
X-Remote-User
X-Remote-Group
- 将各种 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 和ServiceAccount
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#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>
其中,用户和用户各有三个令牌,属于同一用户组。alice
bob
666
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 Forbidden
401 Unauthorized
alice
添加并传递 AuthZRole
RoleBinding
作为快速补救措施,我们可以创建一个具有适当权限的角色,并且 然后将用户绑定到此角色 [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,这样 和 都可以访问资源。666
pod-reader
alice
bob
角色绑定规范 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>
结果将是:
ca.pem
:CA 证书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>
对于我们简单的测试用例,我们只需要注意以下字段:
"CN"
: 用户名"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 获得权限。 现在为 创建角色绑定:403
dylan
dylan
<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 支持以下策略,我们在前面的部分中没有提到或详细介绍:
-
请求标头
选择:
--requestheader-client-ca-file=
上kube-apiserver
-
引导令牌 (v1.18+)
允许简化新集群的引导。 这些令牌作为 Secret 存储在 kube-system 中 命名空间,可在其中动态管理和创建它们。
选择:
--enable-bootstrap-token-auth
在kube-apiserver
;--controllers=<others>,tokerncleaner
在 : 上启用 TokenCleaner 控制器。kube-controller-manager
验证器验证为 。它包括 在组中。
system:bootstrap:<Token ID>
system:bootstrappers
-
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
-
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
-
选择:
--proxy-client-cert-file
--proxy-client-key-file
与其他身份验证协议(LDAP、SAML、Kerberos 、 备用 x509 方案等)可以使用身份验证代理来完成 或身份验证 Webhook。
您可以一次启用多种身份验证方法。您应该至少使用两种方法:
- 服务账户的服务账户令牌
- 至少一种其他用户身份验证方法。
启用多个验证器模块时,第一个 成功验证请求短路评估。 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-admin
jane@example.com
UID
:一个字符串,尝试比 username 更一致和唯一。组
:例如 、 .system:masters
devops-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 模块的工作原理以及如何 以管理员或开发人员的身份正确配置、使用和管理它。