关于 Kubernetes 如何实现和控制 API 访问的介绍性材料, 可阅读控制 Kubernetes API 的访问。
参考文档:
关于 Kubernetes 如何实现和控制 API 访问的介绍性材料, 可阅读控制 Kubernetes API 的访问。
参考文档:
本页提供身份认证有关的概述。
所有 Kubernetes 集群都有两类用户:由 Kubernetes 管理的服务账号和普通用户。
Kubernetes 假定普通用户是由一个与集群无关的服务通过以下方式之一进行管理的:
有鉴于此,Kubernetes 并不包含用来代表普通用户账号的对象。 普通用户的信息无法通过 API 调用添加到集群中。
尽管无法通过 API 调用来添加普通用户, Kubernetes 仍然认为能够提供由集群的证书机构签名的合法证书的用户是通过身份认证的用户。 基于这样的配置,Kubernetes 使用证书中的 'subject' 的通用名称(Common Name)字段 (例如,"/CN=bob")来确定用户名。 接下来,基于角色访问控制(RBAC)子系统会确定用户是否有权针对某资源执行特定的操作。 进一步的细节可参阅证书请求 下普通用户主题。
与此不同,服务账号是 Kubernetes API 所管理的用户。它们被绑定到特定的名字空间, 或者由 API 服务器自动创建,或者通过 API 调用创建。服务账号与一组以 Secret 保存的凭据相关,这些凭据会被挂载到 Pod 中,从而允许集群内的进程访问 Kubernetes API。
API 请求则或者与某普通用户相关联,或者与某服务账号相关联,
亦或者被视作匿名请求。这意味着集群内外的每个进程在向 API
服务器发起请求时都必须通过身份认证,否则会被视作匿名用户。这里的进程可以是在某工作站上输入
kubectl
命令的操作人员,也可以是节点上的 kubelet
组件,还可以是控制面的成员。
Kubernetes 通过身份认证插件利用客户端证书、持有者令牌(Bearer Token)或身份认证代理(Proxy) 来认证 API 请求的身份。HTTP 请求发给 API 服务器时,插件会将以下属性关联到请求本身:
kube-admin
或 jane@example.com
。system:masters
或者 devops-team
等。所有(属性)值对于身份认证系统而言都是不透明的, 只有被鉴权组件解释过之后才有意义。
你可以同时启用多种身份认证方法,并且你通常会至少使用两种方法:
当集群中启用了多个身份认证模块时,第一个成功地对请求完成身份认证的模块会直接做出评估决定。 API 服务器并不保证身份认证模块的运行顺序。
对于所有通过身份认证的用户,system:authenticated
组都会被添加到其组列表中。
与其它身份认证协议(LDAP、SAML、Kerberos、X509 的替代模式等等) 都可以通过使用一个身份认证代理或身份认证 Webhoook 来实现。
通过给 API 服务器传递 --client-ca-file=SOMEFILE
选项,就可以启动客户端证书身份认证。
所引用的文件必须包含一个或者多个证书机构,用来验证向 API 服务器提供的客户端证书。
如果提供了客户端证书并且证书被验证通过,则 subject 中的公共名称(Common Name)
就被作为请求的用户名。
自 Kubernetes 1.4 开始,客户端证书还可以通过证书的 organization 字段标明用户的组成员信息。
要包含用户的多个组成员信息,可以在证书中包含多个 organization 字段。
例如,使用 openssl
命令行工具生成一个证书签名请求:
openssl req -new -key jbeda.pem -out jbeda-csr.pem -subj "/CN=jbeda/O=app1/O=app2"
此命令将使用用户名 jbeda
生成一个证书签名请求(CSR),且该用户属于 "app1" 和
"app2" 两个用户组。
参阅管理证书了解如何生成客户端证书。
当 API 服务器的命令行设置了 --token-auth-file=SOMEFILE
选项时,会从文件中读取持有者令牌。
目前,令牌会长期有效,并且在不重启 API 服务器的情况下无法更改令牌列表。
令牌文件是一个 CSV 文件,包含至少 3 个列:令牌、用户名和用户的 UID。 其余列被视为可选的组名。
如果要设置的组名不止一个,则对应的列必须用双引号括起来,例如:
token,user,uid,"group1,group2,group3"
当使用持有者令牌来对某 HTTP 客户端执行身份认证时,API 服务器希望看到一个名为
Authorization
的 HTTP 头,其值格式为 Bearer <token>
。
持有者令牌必须是一个可以放入 HTTP 头部值字段的字符序列,至多可使用 HTTP 的编码和引用机制。
例如:如果持有者令牌为 31ada4fd-adec-460c-809a-9e56ceb75269
,则其出现在 HTTP 头部时如下所示:
Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269
Kubernetes v1.18 [stable]
为了支持平滑地启动引导新的集群,Kubernetes 包含了一种动态管理的持有者令牌类型,
称作 启动引导令牌(Bootstrap Token)。
这些令牌以 Secret 的形式保存在 kube-system
名字空间中,可以被动态管理和创建。
控制器管理器包含的 TokenCleaner
控制器能够在启动引导令牌过期时将其删除。
这些令牌的格式为 [a-z0-9]{6}.[a-z0-9]{16}
。第一个部分是令牌的 ID;
第二个部分是令牌的 Secret。你可以用如下所示的方式来在 HTTP 头部设置令牌:
Authorization: Bearer 781292.db7bc3a58fc5f07e
你必须在 API 服务器上设置 --enable-bootstrap-token-auth
标志来启用基于启动引导令牌的身份认证组件。
你必须通过控制器管理器的 --controllers
标志来启用 TokenCleaner 控制器;
这可以通过类似 --controllers=*,tokencleaner
这种设置来做到。
如果你使用 kubeadm
来启动引导新的集群,该工具会帮你完成这些设置。
身份认证组件的认证结果为 system:bootstrap:<令牌 ID>
,该用户属于
system:bootstrappers
用户组。
这里的用户名和组设置都是有意设计成这样,其目的是阻止用户在启动引导集群之后继续使用这些令牌。
这里的用户名和组名可以用来(并且已经被 kubeadm
用来)构造合适的鉴权策略,
以完成启动引导新集群的工作。
请参阅启动引导令牌,
以了解关于启动引导令牌身份认证组件与控制器的更深入的信息,以及如何使用
kubeadm
来管理这些令牌。
服务账号(Service Account)是一种自动被启用的用户认证机制,使用经过签名的持有者令牌来验证请求。 该插件可接受两个可选参数:
--service-account-key-file
文件包含 PEM 编码的 x509 RSA 或 ECDSA 私钥或公钥,
用于验证 ServiceAccount 令牌。这样指定的文件可以包含多个密钥,
并且可以使用不同的文件多次指定此参数。若未指定,则使用 --tls-private-key-file 参数。--service-account-lookup
如果启用,则从 API 删除的令牌会被回收。服务账号通常由 API 服务器自动创建并通过 ServiceAccount
准入控制器关联到集群中运行的 Pod 上。
持有者令牌会挂载到 Pod 中可预知的位置,允许集群内进程与 API 服务器通信。
服务账号也可以使用 Pod 规约的 serviceAccountName
字段显式地关联到 Pod 上。
serviceAccountName
通常会被忽略,因为关联关系是自动建立的。
apiVersion: apps/v1 # 此 apiVersion 从 Kubernetes 1.9 开始可用
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
spec:
replicas: 3
template:
metadata:
# ...
spec:
serviceAccountName: bob-the-bot
containers:
- name: nginx
image: nginx:1.14.2
在集群外部使用服务账号持有者令牌也是完全合法的,且可用来为长时间运行的、需要与 Kubernetes
API 服务器通信的任务创建标识。要手动创建服务账号,可以使用
kubectl create serviceaccount <名称>
命令。
此命令会在当前的名字空间中生成一个服务账号。
kubectl create serviceaccount jenkins
serviceaccount/jenkins created
创建相关联的令牌:
kubectl create token jenkins
eyJhbGciOiJSUzI1NiIsImtp...
所创建的令牌是一个已签名的 JWT 令牌。
已签名的 JWT 可以用作持有者令牌,并将被认证为所给的服务账号。 关于如何在请求中包含令牌,请参阅前文。 通常,这些令牌数据会被挂载到 Pod 中以便集群内访问 API 服务器时使用, 不过也可以在集群外部使用。
服务账号被身份认证后,所确定的用户名为 system:serviceaccount:<名字空间>:<服务账号>
,
并被分配到用户组 system:serviceaccounts
和 system:serviceaccounts:<名字空间>
。
由于服务账号令牌也可以保存在 Secret API 对象中,任何能够写入这些 Secret 的用户都可以请求一个令牌,且任何能够读取这些 Secret 的用户都可以被认证为对应的服务账号。 在为用户授予访问服务账号的权限以及对 Secret 的读取或写入权能时,要格外小心。
OpenID Connect 是一种 OAuth2 认证方式, 被某些 OAuth2 提供者支持,例如 Microsoft Entra ID、Salesforce 和 Google。 协议对 OAuth2 的主要扩充体现在有一个附加字段会和访问令牌一起返回, 这一字段称作 ID Token(ID 令牌)。 ID 令牌是一种由服务器签名的 JWT 令牌,其中包含一些可预知的字段, 例如用户的邮箱地址,
要识别用户,身份认证组件使用 OAuth2
令牌响应中的
id_token
(而非 access_token
)作为持有者令牌。
关于如何在请求中设置令牌,可参见前文。
登录到你的身份服务(Identity Provider)
你的身份服务将为你提供 access_token
、id_token
和 refresh_token
在使用 kubectl
时,将 id_token
设置为 --token
标志值,或者将其直接添加到
kubeconfig
中
kubectl
将你的 id_token
放到一个称作 Authorization
的头部,发送给 API 服务器
API 服务器将确保 JWT 的签名是有效的
检查确认 id_token
尚未过期
如果使用 AuthenticationConfiguration
配置了 CEL 表达式,则执行声明和/或用户验证。
确认用户有权限执行操作
鉴权成功之后,API 服务器向 kubectl
返回响应
kubectl
向用户提供反馈信息
由于用来验证你是谁的所有数据都在 id_token
中,Kubernetes 不需要再去联系身份服务。
在一个所有请求都是无状态请求的模型中,这一工作方式可以使得身份认证的解决方案更容易处理大规模请求。
不过,此访问也有一些挑战:
id_token
令牌不可收回。因其属性类似于证书,其生命期一般很短(只有几分钟),
所以,每隔几分钟就要获得一个新的令牌这件事可能很让人头疼。kubectl proxy
命令或者一个能够注入 id_token
的反向代理。要启用此插件,须在 API 服务器上配置以下标志:
参数 | 描述 | 示例 | 必需? |
---|---|---|---|
--oidc-issuer-url | 允许 API 服务器发现公开的签名密钥的服务的 URL。只接受模式为 https:// 的 URL。此值通常设置为服务的发现 URL,已更改为空路径。 | 如果发行人的 OIDC 发现 URL 是 https://accounts.google.com/.well-known/openid-configuration ,则此值应为 https://accounts.provider.example | 是 |
--oidc-client-id | 所有令牌都应发放给此客户 ID。 | kubernetes | 是 |
--oidc-username-claim | 用作用户名的 JWT 申领(JWT Claim)。默认情况下使用 sub 值,即最终用户的一个唯一的标识符。管理员也可以选择其他申领,例如 email 或者 name ,取决于所用的身份服务。不过,除了 email 之外的申领都会被添加令牌发放者的 URL 作为前缀,以免与其他插件产生命名冲突。 | sub | 否 |
--oidc-username-prefix | 要添加到用户名申领之前的前缀,用来避免与现有用户名发生冲突(例如:system: 用户)。例如,此标志值为 oidc: 时将创建形如 oidc:jane.doe 的用户名。如果此标志未设置,且 --oidc-username-claim 标志值不是 email ,则默认前缀为 <令牌发放者的 URL># ,其中 <令牌发放者 URL > 的值取自 --oidc-issuer-url 标志的设定。此标志值为 - 时,意味着禁止添加用户名前缀。 | oidc: | 否 |
--oidc-groups-claim | 用作用户组名的 JWT 申领。如果所指定的申领确实存在,则其值必须是一个字符串数组。 | groups | 否 |
--oidc-groups-prefix | 添加到组申领的前缀,用来避免与现有用户组名(如:system: 组)发生冲突。例如,此标志值为 oidc: 时,所得到的用户组名形如 oidc:engineering 和 oidc:infra 。 | oidc: | 否 |
--oidc-required-claim | 取值为一个 key=value 偶对,意为 ID 令牌中必须存在的申领。如果设置了此标志,则 ID 令牌会被检查以确定是否包含取值匹配的申领。此标志可多次重复,以指定多个申领。 | claim=value | 否 |
--oidc-ca-file | 指向一个 CA 证书的路径,该 CA 负责对你的身份服务的 Web 证书提供签名。默认值为宿主系统的根 CA。 | /etc/kubernetes/ssl/kc-ca.pem | 否 |
--oidc-signing-algs | 采纳的签名算法。默认为 "RS256"。 | RS512 | 否 |
Kubernetes v1.30 [beta]
(enabled by default: true)JWT Authenticator 是一个使用 JWT 兼容令牌对 Kubernetes 用户进行身份认证的认证组件。 认证组件将尝试解析原始 ID 令牌,验证它是否是由所配置的颁发者签名。 用于验证签名的公钥是使用 OIDC 发现从发行者的公共端点发现的。
最小有效 JWT 负载必须包含以下声明:
{
"iss": "https://example.com", // 必须与 issuer.url 匹配
"aud": ["my-app"], // issuer.audiences 中至少一项必须与所提供的 JWT 中的 "aud" 声明相匹配。
"exp": 1234567890, // 令牌过期时间为 UNIX 时间(自 1970 年 1 月 1 日 UTC 以来经过的秒数)
"<username-claim>": "user" // 这是在 claimMappings.username.claim 或 claimMappings.username.expression 中配置的用户名声明
}
配置文件方法允许你配置多个 JWT 认证组件,每个身份认证组件都有唯一的 issuer.url
和 issuer.discoveryURL
。
配置文件甚至允许你指定 CEL
表达式以将声明映射到用户属性,并验证声明和用户信息。
当配置文件修改时,API 服务器还会自动重新加载认证组件。
你可以使用 apiserver_authentication_config_controller_automatic_reload_last_timestamp_seconds
指标来监控 API 服务器上次重新加载配置的时间。
你必须使用 API 服务器上的 --authentication-config
标志指定身份认证配置的路径。
如果你想使用命令行标志而不是配置文件,命令行标志仍然有效。
要使用新功能(例如配置多个认证组件、为发行者设置多个受众),请切换到使用配置文件。
对于 Kubernetes v1.31,
结构化身份认证配置文件格式是 Beta 级别,并且使用该配置的机制也是 Beta 级别。
如果你没有禁用集群的 StructuredAuthenticationConfiguration
特性门控,
则可以通过为 kube-apiserver 指定 --authentication-config
命令行参数来启用结构化身份认证。
下面给出的是一个结构化身份认证配置文件的示例:
你不能同时指定 --authentication-config
和 --oidc-*
命令行参数,
否则API服务器会报告错误,然后立即退出。
如果你想切换到使用结构化身份认证配置,则必须删除 --oidc-*
命令行参数,并改用配置文件。
---
#
# 注意:这是一个示例配置,不要将其用于你自己的集群!
#
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
# 使用 JWT 兼容令牌对 Kubernetes 用户进行身份认证的认证组件列表,允许的最大认证组件数量为 64。
jwt:
- issuer:
# url 在所有认证组件中必须是唯一的。
# url 不得与 --service-account-issuer 中配置的颁发者冲突。
url: https://example.com # 与 --oidc-issuer-url 一致。
# discoveryURL(如果指定)将覆盖用于获取发现信息的 URL,而不是使用 “{url}/.well-known/openid-configuration”。
# 系统会使用所给的配置值,因此如果需要,“/.well-known/openid-configuration” 必须包含在 discoveryURL 中。
#
# 取回的发现信息中的 “issuer” 字段必须与 AuthenticationConfiguration 中的
# “issuer.url” 字段匹配,并被用于验证所呈现的 JWT 中的 “iss” 声明。
# 这适用于众所周知的端点和 jwks 端点托管在与颁发者不同的位置(例如集群本地)的场景。
# discoveryURL 必须与 url 不同(如果指定),并且在所有认证组件中必须是唯一的。
discoveryURL: https://discovery.example.com/.well-known/openid-configuration
# PEM 编码的 CA 证书用于在获取发现信息时验证连接。
# 如果未设置,将使用系统验证程序。
# 与 --oidc-ca-file 标志引用的文件内容的值相同。
certificateAuthority: <PEM encoded CA certificates>
# audiences 是 JWT 必须发布给的一组可接受的受众。
# 至少其中一项必须与所提供的 JWT 中的 “aud” 声明相匹配。
audiences:
- my-app # 与 --oidc-client-id 一致。
- my-other-app
# 当指定多个受众时,需要将此字段设置为 “MatchAny”。
audienceMatchPolicy: MatchAny
# 用于验证令牌声明以对用户进行身份认证的规则。
claimValidationRules:
# 与 --oidc-required-claim key=value 一致
- claim: hd
requiredValue: example.com
# 你可以使用表达式来验证声明,而不是仅仅靠 claim 和 requiredValue 来执行检查。
# expression 是一个计算结果为布尔值的 CEL 表达式。
# 所有表达式的计算结果必须为 true 才能使验证成功。
- expression: 'claims.hd == "example.com"'
# message 用来定制验证失败时在 API 服务器日志中看到的错误消息。
message: the hd claim must be set to example.com
- expression: 'claims.exp - claims.nbf <= 86400'
message: total token lifetime must not exceed 24 hours
claimMappings:
# username 表示用户名属性的选项。
# 这是唯一必需的属性。
username:
# 与 --oidc-username-claim 相同,与 username.expression 互斥。
claim: "sub"
# 与 --oidc-username-prefix 相同,与 username.expression 互斥。
# 如果设置了username.claim,则需要username.prefix。
# 如果不需要前缀,可显式将其设置为 ""。
prefix: ""
# 与 username.claim 和 username.prefix 互斥。
# expression 是计算结果为字符串的 CEL 表达式。
#
# 1. 如果 username.expression 使用 “claims.email”,则必须在 username.expression
# 或 extra[*].valueExpression 或 ClaimValidationRules[*].expression 中使用 “claims.email_verified”。
# 与 username.claim 设置为 “email” 时自动应用的验证相匹配的示例声明验证规则表达式是
# “claims.?email_verified.orValue(true)”。
# 2. 如果根据 username.expression 断言的用户名是空字符串,则身份认证请求将失败。
expression: 'claims.username + ":external-user"'
# groups 代表 groups 属性的一个选项。
groups:
# 与 --oidc-groups-claim 相同,与 groups.express 互斥。
claim: "sub"
# 与 --oidc-groups-prefix 相同。与 groups.express 互斥。
# 如果设置了 groups.claim,则需要 groups.prefix。
# 如果不需要前缀,则显式将其设置为 ""。
prefix: ""
# 与 groups.claim 和 groups.prefix 互斥。
# expression 是一个计算结果为字符串或字符串列表的 CEL 表达式。
expression: 'claims.roles.split(",")'
# uid 表示 uid 属性的一个选项。
uid:
# 与 uid.expression 互斥。
claim: 'sub'
# 与 uid.claim 互斥
# expression 是计算结果为字符串的 CEL 表达式。
expression: 'claims.sub'
# 要添加到 UserInfo 对象的其他属性,键必须是域前缀路径并且必须是唯一的。
extra:
- key: 'example.com/tenant'
# valueExpression 是一个计算结果为字符串或字符串列表的 CEL 表达式。
valueExpression: 'claims.tenant'
# 应用于最终用户对象的验证规则。
userValidationRules:
# expression 是一个计算结果为布尔值的 CEL 表达式。
# 所有表达式的计算结果必须为 true,用户才有效。
- expression: "!user.username.startsWith('system:')"
# Message 自定义验证失败时在 API 服务器日志中看到的错误消息。
message: 'username cannot used reserved system: prefix'
- expression: "user.groups.all(group, !group.startsWith('system:'))"
message: 'groups cannot used reserved system: prefix'
jwt.claimValidationRules[i].expression
表示将由 CEL 计算的表达式。
CEL 表达式可以访问令牌有效负载的内容,这些内容被组织成 claims
CEL 变量。
claims
是声明名称(作为字符串)到声明值(任何类型)的映射。
jwt.userValidationRules[i].expression
表示将由 CEL 计算的表达式。
CEL 表达式可以访问 userInfo
的内容,并组织成 user
CEL 变量。
有关 user
的架构,请参阅
[UserInfo](/zh-cn/docs/reference/ generated/kubernetes-api/v1.31/#userinfo-v1-authentication-k8s-io) API 文档。
声明映射表达式
jwt.claimMappings.username.expression
、jwt.claimMappings.groups.expression
、
jwt.claimMappings.uid.expression
jwt.claimMappings.extra[i].valueExpression
表示将由 CEL 计算的表达式。
CEL 表达式可以访问令牌有效负载的内容,这些内容被组织成 claims
CEL 变量。
claims
是声明名称(作为字符串)到声明值(任何类型)的映射。
要了解更多信息,请参阅CEL 文档。
以下是具有不同令牌有效负载的 “AuthenticationConfiguration” 示例。
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
jwt:
- issuer:
url: https://example.com
audiences:
- my-app
claimMappings:
username:
expression: 'claims.username + ":external-user"'
groups:
expression: 'claims.roles.split(",")'
uid:
expression: 'claims.sub'
extra:
- key: 'example.com/tenant'
valueExpression: 'claims.tenant'
userValidationRules:
- expression: "!user.username.startsWith('system:')" # 表达式的计算结果为 true,因此验证将成功。
message: 'username cannot used reserved system: prefix'
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6ImY3dF9tOEROWmFTQk1oWGw5QXZTWGhBUC04Y0JmZ0JVbFVpTG5oQkgxdXMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJrdWJlcm5ldGVzIiwiZXhwIjoxNzAzMjMyOTQ5LCJpYXQiOjE3MDExMDcyMzMsImlzcyI6Imh0dHBzOi8vZXhhbXBsZS5jb20iLCJqdGkiOiI3YzMzNzk0MjgwN2U3M2NhYTJjMzBjODY4YWMwY2U5MTBiY2UwMmRkY2JmZWJlOGMyM2I4YjVmMjdhZDYyODczIiwibmJmIjoxNzAxMTA3MjMzLCJyb2xlcyI6InVzZXIsYWRtaW4iLCJzdWIiOiJhdXRoIiwidGVuYW50IjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjRhIiwidXNlcm5hbWUiOiJmb28ifQ.TBWF2RkQHm4QQz85AYPcwLxSk-VLvQW-mNDHx7SEOSv9LVwcPYPuPajJpuQn9C_gKq1R94QKSQ5F6UgHMILz8OfmPKmX_00wpwwNVGeevJ79ieX2V-__W56iNR5gJ-i9nn6FYk5pwfVREB0l4HSlpTOmu80gbPWAXY5hLW0ZtcE1JTEEmefORHV2ge8e3jp1xGafNy6LdJWabYuKiw8d7Qga__HxtKB-t0kRMNzLRS7rka_SfQg0dSYektuxhLbiDkqhmRffGlQKXGVzUsuvFw7IGM5ZWnZgEMDzCI357obHeM3tRqpn5WRjtB8oM7JgnCymaJi-P3iCd88iu1xnzA
其中令牌有效负载是:
{
"aud": "kubernetes",
"exp": 1703232949,
"iat": 1701107233,
"iss": "https://example.com",
"jti": "7c337942807e73caa2c30c868ac0ce910bce02ddcbfebe8c23b8b5f27ad62873",
"nbf": 1701107233,
"roles": "user,admin",
"sub": "auth",
"tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a",
"username": "foo"
}
具有上述 AuthenticationConfiguration
的令牌将生成以下 UserInfo
对象并成功对用户进行身份认证。
{
"username": "foo:external-user",
"uid": "auth",
"groups": [
"user",
"admin"
],
"extra": {
"example.com/tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a"
}
}
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
jwt:
- issuer:
url: https://example.com
audiences:
- my-app
claimValidationRules:
- expression: 'claims.hd == "example.com"' # 下面的令牌没有此声明,因此验证将失败。
message: the hd claim must be set to example.com
claimMappings:
username:
expression: 'claims.username + ":external-user"'
groups:
expression: 'claims.roles.split(",")'
uid:
expression: 'claims.sub'
extra:
- key: 'example.com/tenant'
valueExpression: 'claims.tenant'
userValidationRules:
- expression: "!user.username.startsWith('system:')" # 该表达式的计算结果将为 true,因此验证将会成功。
message: 'username cannot used reserved system: prefix'
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6ImY3dF9tOEROWmFTQk1oWGw5QXZTWGhBUC04Y0JmZ0JVbFVpTG5oQkgxdXMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJrdWJlcm5ldGVzIiwiZXhwIjoxNzAzMjMyOTQ5LCJpYXQiOjE3MDExMDcyMzMsImlzcyI6Imh0dHBzOi8vZXhhbXBsZS5jb20iLCJqdGkiOiI3YzMzNzk0MjgwN2U3M2NhYTJjMzBjODY4YWMwY2U5MTBiY2UwMmRkY2JmZWJlOGMyM2I4YjVmMjdhZDYyODczIiwibmJmIjoxNzAxMTA3MjMzLCJyb2xlcyI6InVzZXIsYWRtaW4iLCJzdWIiOiJhdXRoIiwidGVuYW50IjoiNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjRhIiwidXNlcm5hbWUiOiJmb28ifQ.TBWF2RkQHm4QQz85AYPcwLxSk-VLvQW-mNDHx7SEOSv9LVwcPYPuPajJpuQn9C_gKq1R94QKSQ5F6UgHMILz8OfmPKmX_00wpwwNVGeevJ79ieX2V-__W56iNR5gJ-i9nn6FYk5pwfVREB0l4HSlpTOmu80gbPWAXY5hLW0ZtcE1JTEEmefORHV2ge8e3jp1xGafNy6LdJWabYuKiw8d7Qga__HxtKB-t0kRMNzLRS7rka_SfQg0dSYektuxhLbiDkqhmRffGlQKXGVzUsuvFw7IGM5ZWnZgEMDzCI357obHeM3tRqpn5WRjtB8oM7JgnCymaJi-P3iCd88iu1xnzA
其中令牌有效负载是:
{
"aud": "kubernetes",
"exp": 1703232949,
"iat": 1701107233,
"iss": "https://example.com",
"jti": "7c337942807e73caa2c30c868ac0ce910bce02ddcbfebe8c23b8b5f27ad62873",
"nbf": 1701107233,
"roles": "user,admin",
"sub": "auth",
"tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a",
"username": "foo"
}
具有上述 AuthenticationConfiguration
的令牌将无法进行身份认证,
因为 hd
声明未设置为 example.com
。API 服务器将返回 401 Unauthorized
错误。
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
jwt:
- issuer:
url: https://example.com
audiences:
- my-app
claimValidationRules:
- expression: 'claims.hd == "example.com"'
message: the hd claim must be set to example.com
claimMappings:
username:
expression: '"system:" + claims.username' # 这将为用户名添加前缀 “system:”,并且用户验证将失败。
groups:
expression: 'claims.roles.split(",")'
uid:
expression: 'claims.sub'
extra:
- key: 'example.com/tenant'
valueExpression: 'claims.tenant'
userValidationRules:
- expression: "!user.username.startsWith('system:')" # 用户名将为 system:foo 并且表达式将计算为 false,因此验证将失败。
message: 'username cannot used reserved system: prefix'
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6ImY3dF9tOEROWmFTQk1oWGw5QXZTWGhBUC04Y0JmZ0JVbFVpTG5oQkgxdXMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJrdWJlcm5ldGVzIiwiZXhwIjoxNzAzMjMyOTQ5LCJoZCI6ImV4YW1wbGUuY29tIiwiaWF0IjoxNzAxMTEzMTAxLCJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwianRpIjoiYjViMDY1MjM3MmNkMjBlMzQ1YjZmZGZmY2RjMjE4MWY0YWZkNmYyNTlhYWI0YjdlMzU4ODEyMzdkMjkyMjBiYyIsIm5iZiI6MTcwMTExMzEwMSwicm9sZXMiOiJ1c2VyLGFkbWluIiwic3ViIjoiYXV0aCIsInRlbmFudCI6IjcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0YSIsInVzZXJuYW1lIjoiZm9vIn0.FgPJBYLobo9jnbHreooBlvpgEcSPWnKfX6dc0IvdlRB-F0dCcgy91oCJeK_aBk-8zH5AKUXoFTlInfLCkPivMOJqMECA1YTrMUwt_IVqwb116AqihfByUYIIqzMjvUbthtbpIeHQm2fF0HbrUqa_Q0uaYwgy8mD807h7sBcUMjNd215ff_nFIHss-9zegH8GI1d9fiBf-g6zjkR1j987EP748khpQh9IxPjMJbSgG_uH5x80YFuqgEWwq-aYJPQxXX6FatP96a2EAn7wfPpGlPRt0HcBOvq5pCnudgCgfVgiOJiLr_7robQu4T1bis0W75VPEvwWtgFcLnvcQx0JWg
其中令牌有效负载是:
{
"aud": "kubernetes",
"exp": 1703232949,
"hd": "example.com",
"iat": 1701113101,
"iss": "https://example.com",
"jti": "b5b0652372cd20e345b6fdffcdc2181f4afd6f259aab4b7e35881237d29220bc",
"nbf": 1701113101,
"roles": "user,admin",
"sub": "auth",
"tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a",
"username": "foo"
}
具有上述 “AuthenticationConfiguration” 的令牌将生成以下 “UserInfo” 对象:
{
"username": "system:foo",
"uid": "auth",
"groups": [
"user",
"admin"
],
"extra": {
"example.com/tenant": "72f988bf-86f1-41af-91ab-2d7cd011db4a"
}
}
这将导致用户验证失败,因为用户名以 system:
开头。 API 服务器将返回 401 Unauthorized
错误。
issuer.url
和 issuer.discoveryURL
的出口选择器配置。Kubernetes 并未提供 OpenID Connect 的身份服务。 你可以使用现有的公共的 OpenID Connect 身份服务 (例如 Google 或者其他服务)。 或者,你也可以选择自己运行一个身份服务,例如 dex、 Keycloak、 CloudFoundry UAA 或者 Tremolo Security 的 OpenUnison。
要在 Kubernetes 环境中使用某身份服务,该服务必须:
用于验证签名的公钥是使用 OIDC 发现从发行者的公共端点发现的。
如果你使用身份认证配置文件,则身份提供者不需要公开发布发现端点。
你可以将发现端点托管在与颁发者不同的位置(例如集群本地),并在配置文件中指定 issuer.discoveryURL
。
关于上述第三条需求,即要求具备 CA 签名的证书,有一些额外的注意事项。
如果你部署了自己的身份服务,而不是使用云厂商(如 Google 或 Microsoft)所提供的服务,
你必须对身份服务的 Web 服务器证书进行签名,签名所用证书的 CA
标志要设置为
TRUE
,即使用的是自签名证书。这是因为 GoLang 的 TLS 客户端实现对证书验证标准方面有非常严格的要求。
如果你手头没有现成的 CA 证书,可以使用 Dex
团队所开发的证书生成脚本
来创建一个简单的 CA 和被签了名的证书与密钥对。
或者你也可以使用这个类似的脚本,
生成一个合法期更长、密钥尺寸更大的 SHA256 证书。
参阅特定系统的安装指令:
第一种方案是使用 kubectl 的 oidc
身份认证组件,该组件将 id_token
设置为所有请求的持有者令牌,
并且在令牌过期时自动刷新。在你登录到你的身份服务之后,
可以使用 kubectl 来添加你的 id_token
、refresh_token
、client_id
和
client_secret
,以配置该插件。
如果服务在其刷新令牌响应中不包含 id_token
,则此插件无法支持该服务。
这时你应该考虑下面的选项二。
kubectl config set-credentials USER_NAME \
--auth-provider=oidc \
--auth-provider-arg=idp-issuer-url=( issuer url ) \
--auth-provider-arg=client-id=( your client id ) \
--auth-provider-arg=client-secret=( your client secret ) \
--auth-provider-arg=refresh-token=( your refresh token ) \
--auth-provider-arg=idp-certificate-authority=( path to your ca certificate ) \
--auth-provider-arg=id-token=( your id_token )
作为示例,在完成对你的身份服务的身份认证之后,运行下面的命令:
kubectl config set-credentials mmosley \
--auth-provider=oidc \
--auth-provider-arg=idp-issuer-url=https://oidcidp.tremolo.lan:8443/auth/idp/OidcIdP \
--auth-provider-arg=client-id=kubernetes \
--auth-provider-arg=client-secret=1db158f6-177d-4d9c-8a8b-d36869918ec5 \
--auth-provider-arg=refresh-token=q1bKLFOyUiosTfawzA93TzZIDzH2TNa2SMm0zEiPKTUwME6BkEo6Sql5yUWVBSWpKUGphaWpxSVAfekBOZbBhaEW+VlFUeVRGcluyVF5JT4+haZmPsluFoFu5XkpXk5BXqHega4GAXlF+ma+vmYpFcHe5eZR+slBFpZKtQA= \
--auth-provider-arg=idp-certificate-authority=/root/ca.pem \
--auth-provider-arg=id-token=eyJraWQiOiJDTj1vaWRjaWRwLnRyZW1vbG8ubGFuLCBPVT1EZW1vLCBPPVRybWVvbG8gU2VjdXJpdHksIEw9QXJsaW5ndG9uLCBTVD1WaXJnaW5pYSwgQz1VUy1DTj1rdWJlLWNhLTEyMDIxNDc5MjEwMzYwNzMyMTUyIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL29pZGNpZHAudHJlbW9sby5sYW46ODQ0My9hdXRoL2lkcC9PaWRjSWRQIiwiYXVkIjoia3ViZXJuZXRlcyIsImV4cCI6MTQ4MzU0OTUxMSwianRpIjoiMm96US15TXdFcHV4WDlHZUhQdy1hZyIsImlhdCI6MTQ4MzU0OTQ1MSwibmJmIjoxNDgzNTQ5MzMxLCJzdWIiOiI0YWViMzdiYS1iNjQ1LTQ4ZmQtYWIzMC0xYTAxZWU0MWUyMTgifQ.w6p4J_6qQ1HzTG9nrEOrubxIMb9K5hzcMPxc9IxPx2K4xO9l-oFiUw93daH3m5pluP6K7eOE6txBuRVfEcpJSwlelsOsW8gb8VJcnzMS9EnZpeA0tW_p-mnkFc3VcfyXuhe5R3G7aa5d8uHv70yJ9Y3-UhjiN9EhpMdfPAoEB9fYKKkJRzF7utTTIPGrSaSU6d2pcpfYKaxIwePzEkT4DfcQthoZdy9ucNvvLoi1DIC-UocFD8HLs8LYKEqSxQvOcvnThbObJ9af71EwmuE21fO5KzMW20KtAeget1gnldOosPtz1G5EwvaQ401-RPQzPGMVBld0_zMCAwZttJ4knw
此操作会生成以下配置:
users:
- name: mmosley
user:
auth-provider:
config:
client-id: kubernetes
client-secret: 1db158f6-177d-4d9c-8a8b-d36869918ec5
id-token: eyJraWQiOiJDTj1vaWRjaWRwLnRyZW1vbG8ubGFuLCBPVT1EZW1vLCBPPVRybWVvbG8gU2VjdXJpdHksIEw9QXJsaW5ndG9uLCBTVD1WaXJnaW5pYSwgQz1VUy1DTj1rdWJlLWNhLTEyMDIxNDc5MjEwMzYwNzMyMTUyIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL29pZGNpZHAudHJlbW9sby5sYW46ODQ0My9hdXRoL2lkcC9PaWRjSWRQIiwiYXVkIjoia3ViZXJuZXRlcyIsImV4cCI6MTQ4MzU0OTUxMSwianRpIjoiMm96US15TXdFcHV4WDlHZUhQdy1hZyIsImlhdCI6MTQ4MzU0OTQ1MSwibmJmIjoxNDgzNTQ5MzMxLCJzdWIiOiI0YWViMzdiYS1iNjQ1LTQ4ZmQtYWIzMC0xYTAxZWU0MWUyMTgifQ.w6p4J_6qQ1HzTG9nrEOrubxIMb9K5hzcMPxc9IxPx2K4xO9l-oFiUw93daH3m5pluP6K7eOE6txBuRVfEcpJSwlelsOsW8gb8VJcnzMS9EnZpeA0tW_p-mnkFc3VcfyXuhe5R3G7aa5d8uHv70yJ9Y3-UhjiN9EhpMdfPAoEB9fYKKkJRzF7utTTIPGrSaSU6d2pcpfYKaxIwePzEkT4DfcQthoZdy9ucNvvLoi1DIC-UocFD8HLs8LYKEqSxQvOcvnThbObJ9af71EwmuE21fO5KzMW20KtAeget1gnldOosPtz1G5EwvaQ401-RPQzPGMVBld0_zMCAwZttJ4knw
idp-certificate-authority: /root/ca.pem
idp-issuer-url: https://oidcidp.tremolo.lan:8443/auth/idp/OidcIdP
refresh-token: q1bKLFOyUiosTfawzA93TzZIDzH2TNa2SMm0zEiPKTUwME6BkEo6Sql5yUWVBSWpKUGphaWpxSVAfekBOZbBhaEW+VlFUeVRGcluyVF5JT4+haZmPsluFoFu5XkpXk5BXq
name: oidc
当你的 id_token
过期时,kubectl
会尝试使用你的 refresh_token
来刷新你的
id_token
,并且在 .kube/config
文件的 client_secret
中存放 refresh_token
和 id_token
的新值。
--token
选项kubectl
命令允许你使用 --token
选项传递一个令牌。
你可以将 id_token
的内容复制粘贴过来,作为此标志的取值:
kubectl --token=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL21sYi50cmVtb2xvLmxhbjo4MDQzL2F1dGgvaWRwL29pZGMiLCJhdWQiOiJrdWJlcm5ldGVzIiwiZXhwIjoxNDc0NTk2NjY5LCJqdGkiOiI2RDUzNXoxUEpFNjJOR3QxaWVyYm9RIiwiaWF0IjoxNDc0NTk2MzY5LCJuYmYiOjE0NzQ1OTYyNDksInN1YiI6Im13aW5kdSIsInVzZXJfcm9sZSI6WyJ1c2VycyIsIm5ldy1uYW1lc3BhY2Utdmlld2VyIl0sImVtYWlsIjoibXdpbmR1QG5vbW9yZWplZGkuY29tIn0.f2As579n9VNoaKzoF-dOQGmXkFKf1FMyNV0-va_B63jn-_n9LGSCca_6IVMP8pO-Zb4KvRqGyTP0r3HkHxYy5c81AnIh8ijarruczl-TK_yF5akjSTHFZD-0gRzlevBDiH8Q79NAr-ky0P4iIXS8lY9Vnjch5MF74Zx0c3alKJHJUnnpjIACByfF2SCaYzbWFMUNat-K1PaUk5-ujMBG7yYnr95xD-63n8CO8teGUAAEMx6zRjzfhnhbzX-ajwZLGwGUBT4WqjMs70-6a7_8gZmLZb2az1cZynkFRj2BaCkVT3A2RrjeEwZEtGXlMqKJ1_I2ulrOVsYx01_yD35-rw get nodes
Webhook 身份认证是一种用来验证持有者令牌的回调机制。
--authentication-token-webhook-config-file
指向一个配置文件,
其中描述如何访问远程的 Webhook 服务。--authentication-token-webhook-cache-ttl
用来设定身份认证决定的缓存时间。
默认时长为 2 分钟。--authentication-token-webhook-version
决定是使用 authentication.k8s.io/v1beta1
还是
authenticationk8s.io/v1
版本的 TokenReview
对象从 Webhook 发送/接收信息。
默认为“v1beta1”。配置文件使用 kubeconfig
文件的格式。文件中,clusters
指代远程服务,users
指代远程 API 服务
Webhook。下面是一个例子:
# Kubernetes API 版本
apiVersion: v1
# API 对象类别
kind: Config
# clusters 指代远程服务
clusters:
- name: name-of-remote-authn-service
cluster:
certificate-authority: /path/to/ca.pem # 用来验证远程服务的 CA
server: https://authn.example.com/authenticate # 要查询的远程服务 URL。生产环境中建议使用 'https'。
# users 指代 API 服务的 Webhook 配置
users:
- name: name-of-api-server
user:
client-certificate: /path/to/cert.pem # Webhook 插件要使用的证书
client-key: /path/to/key.pem # 与证书匹配的密钥
# kubeconfig 文件需要一个上下文(Context),此上下文用于本 API 服务器
current-context: webhook
contexts:
- context:
cluster: name-of-remote-authn-service
user: name-of-api-server
name: webhook
当客户端尝试在 API 服务器上使用持有者令牌完成身份认证
(如前所述)时,
身份认证 Webhook 会用 POST 请求发送一个 JSON 序列化的对象到远程服务。
该对象是 TokenReview
对象,其中包含持有者令牌。
Kubernetes 不会强制请求提供此 HTTP 头部。
要注意的是,Webhook API 对象和其他 Kubernetes API 对象一样,
也要受到同一版本兼容规则约束。
实现者应检查请求的 apiVersion
字段以确保正确的反序列化,
并且 必须 以与请求相同版本的 TokenReview
对象进行响应。
Kubernetes API 服务器默认发送 authentication.k8s.io/v1beta1
令牌以实现向后兼容性。
要选择接收 authentication.k8s.io/v1
令牌认证,API 服务器必须带着参数
--authentication-token-webhook-version=v1
启动。
{
"apiVersion": "authentication.k8s.io/v1",
"kind": "TokenReview",
"spec": {
# 发送到 API 服务器的不透明持有者令牌
"token": "014fbff9a07c...",
# 提供令牌的服务器的受众标识符的可选列表。
# 受众感知令牌认证组件(例如,OIDC 令牌认证组件)
# 应验证令牌是否针对此列表中的至少一个受众,
# 并返回此列表与响应状态中令牌的有效受众的交集。
# 这确保了令牌对于向其提供给的服务器进行身份认证是有效的。
# 如果未提供受众,则应验证令牌以向 Kubernetes API 服务器进行身份认证。
"audiences": ["https://myserver.example.com", "https://myserver.internal.example.com"]
}
}
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"spec": {
# 发送到 API 服务器的不透明匿名令牌
"token": "014fbff9a07c...",
# 提供令牌的服务器的受众标识符的可选列表。
# 受众感知令牌认证组件(例如,OIDC 令牌认证组件)
# 应验证令牌是否针对此列表中的至少一个受众,
# 并返回此列表与响应状态中令牌的有效受众的交集。
# 这确保了令牌对于向其提供给的服务器进行身份认证是有效的。
# 如果未提供受众,则应验证令牌以向 Kubernetes API 服务器进行身份认证。
"audiences": ["https://myserver.example.com", "https://myserver.internal.example.com"]
}
}
远程服务预计会填写请求的 status
字段以指示登录成功。
响应正文的 spec
字段被忽略并且可以省略。
远程服务必须使用它收到的相同 TokenReview
API 版本返回响应。
持有者令牌的成功验证将返回:
{
"apiVersion": "authentication.k8s.io/v1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
# 必要
"username": "janedoe@example.com",
# 可选
"uid": "42",
# 可选的组成员身份
"groups": ["developers", "qa"],
# 认证者提供的可选附加信息。
# 此字段不可包含机密数据,因为这类数据可能被记录在日志或 API 对象中,
# 并且可能传递给 admission webhook。
"extra": {
"extrafield1": [
"extravalue1",
"extravalue2"
]
}
},
# 认证组件可以返回的、可选的用户感知令牌列表,
# 包含令牌对其有效的、包含于 `spec.audiences` 列表中的受众。
# 如果省略,则认为该令牌可用于对 Kubernetes API 服务器进行身份认证。
"audiences": ["https://myserver.example.com"]
}
}
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
# 必要
"username": "janedoe@example.com",
# 可选
"uid": "42",
# 可选的组成员身份
"groups": ["developers", "qa"],
# 认证者提供的可选附加信息。
# 此字段不可包含机密数据,因为这类数据可能被记录在日志或 API 对象中,
# 并且可能传递给 admission webhook。
"extra": {
"extrafield1": [
"extravalue1",
"extravalue2"
]
}
},
# 认证组件可以返回的、可选的用户感知令牌列表,
# 包含令牌对其有效的、包含于 `spec.audiences` 列表中的受众。
# 如果省略,则认为该令牌可用于对 Kubernetes API 服务器进行身份认证。
"audiences": ["https://myserver.example.com"]
}
}
而不成功的请求会返回:
{
"apiVersion": "authentication.k8s.io/v1",
"kind": "TokenReview",
"status": {
"authenticated": false,
# 可选地包括有关身份认证失败原因的详细信息。
# 如果没有提供错误信息,API 将返回一个通用的 Unauthorized 消息。
# 当 authenticated=true 时,error 字段被忽略。
"error": "Credentials are expired"
}
}
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": false,
# 可选地包括有关身份认证失败原因的详细信息。
# 如果没有提供错误信息,API 将返回一个通用的 Unauthorized 消息。
# 当 authenticated=true 时,error 字段被忽略。
"error": "Credentials are expired"
}
}
API 服务器可以配置成从请求的头部字段值(如 X-Remote-User
)中辩识用户。
这一设计是用来与某身份认证代理一起使用 API 服务器,代理负责设置请求的头部字段值。
--requestheader-username-headers
必需字段,大小写不敏感。
用来设置要获得用户身份所要检查的头部字段名称列表(有序)。
第一个包含数值的字段会被用来提取用户名。--requestheader-group-headers
可选字段,在 Kubernetes 1.6 版本以后支持,大小写不敏感。
建议设置为 "X-Remote-Group"。用来指定一组头部字段名称列表,以供检查用户所属的组名称。
所找到的全部头部字段的取值都会被用作用户组名。--requestheader-extra-headers-prefix
可选字段,在 Kubernetes 1.6 版本以后支持,大小写不敏感。
建议设置为 "X-Remote-Extra-"。用来设置一个头部字段的前缀字符串,
API 服务器会基于所给前缀来查找与用户有关的一些额外信息。这些额外信息通常用于所配置的鉴权插件。
API 服务器会将与所给前缀匹配的头部字段过滤出来,去掉其前缀部分,将剩余部分转换为小写字符串,
并在必要时执行百分号解码后,
构造新的附加信息字段键名。原来的头部字段值直接作为附加信息字段的值。在 1.13.3 版本之前(包括 1.10.7、1.9.11),附加字段的键名只能包含 HTTP 头部标签的合法字符。
例如,使用下面的配置:
--requestheader-username-headers=X-Remote-User
--requestheader-group-headers=X-Remote-Group
--requestheader-extra-headers-prefix=X-Remote-Extra-
针对所收到的如下请求:
GET / HTTP/1.1
X-Remote-User: fido
X-Remote-Group: dogs
X-Remote-Group: dachshunds
X-Remote-Extra-Acme.com%2Fproject: some-project
X-Remote-Extra-Scopes: openid
X-Remote-Extra-Scopes: profile
会生成下面的用户信息:
name: fido
groups:
- dogs
- dachshunds
extra:
acme.com/project:
- some-project
scopes:
- openid
- profile
为了防范头部信息侦听,在请求中的头部字段被检视之前, 身份认证代理需要向 API 服务器提供一份合法的客户端证书,供后者使用所给的 CA 来执行验证。 警告:不要在不同的上下文中复用 CA 证书,除非你清楚这样做的风险是什么以及应如何保护 CA 用法的机制。
--requestheader-client-ca-file
必需字段,给出 PEM 编码的证书包。
在检查请求的头部字段以提取用户名信息之前,必须提供一个合法的客户端证书,
且该证书要能够被所给文件中的机构所验证。--requestheader-allowed-names
可选字段,用来给出一组公共名称(CN)。
如果此标志被设置,则在检视请求中的头部以提取用户信息之前,
必须提供包含此列表中所给的 CN 名的、合法的客户端证书。启用匿名请求支持之后,如果请求没有被已配置的其他身份认证方法拒绝,
则被视作匿名请求(Anonymous Requests)。这类请求获得用户名 system:anonymous
和对应的用户组 system:unauthenticated
。
例如,在一个配置了令牌身份认证且启用了匿名访问的服务器上,如果请求提供了非法的持有者令牌,
则会返回 401 Unauthorized
错误。如果请求没有提供持有者令牌,则被视为匿名请求。
在 1.5.1-1.5.x 版本中,匿名访问默认情况下是被禁用的,可以通过为 API 服务器设定
--anonymous-auth=true
来启用。
在 1.6 及之后版本中,如果所使用的鉴权模式不是 AlwaysAllow
,则匿名访问默认是被启用的。
从 1.6 版本开始,ABAC 和 RBAC 鉴权模块要求对 system:anonymous
用户或者
system:unauthenticated
用户组执行显式的权限判定,所以之前的为用户 *
或用户组
*
赋予访问权限的策略规则都不再包含匿名用户。
Kubernetes v1.31 [alpha]
(enabled by default: false)AuthenticationConfiguration
可用于配置匿名身份认证模块。
要通过配置文件启用匿名身份认证配置,你需要启用 AnonymousAuthConfigurableEndpoints
特性门控。
当此特性门控被启用时,你不能设置 --anonymous-auth
标志。
使用身份认证配置文件来配置匿名身份认证模块的主要优点是, 除了启用和禁用匿名身份认证外,你还可以配置哪些端点支持匿名身份认证。
以下是一个身份认证配置文件示例:
---
#
# 注意:这是一个示例配置。
# 请勿将其用于你自己的集群!
#
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthenticationConfiguration
anonymous:
enabled: true
conditions:
- path: /livez
- path: /readyz
- path: /healthz
在上述配置中,只有 /livez
、/readyz
和 /healthz
端点可以通过匿名请求进行访问。
即使 RBAC 配置允许进行匿名请求,也不可以访问任何其他端点。
一个用户可以通过伪装(Impersonation)头部字段来以另一个用户的身份执行操作。 使用这一能力,你可以手动重载请求被身份认证所识别出来的用户信息。 例如,管理员可以使用这一功能特性来临时伪装成另一个用户,查看请求是否被拒绝, 从而调试鉴权策略中的问题,
带伪装的请求首先会被身份认证识别为发出请求的用户, 之后会切换到使用被伪装的用户的用户信息。
以下 HTTP 头部字段可用来执行伪装请求:
Impersonate-User
:要伪装成的用户名Impersonate-Group
:要伪装成的用户组名。可以多次指定以设置多个用户组。
可选字段;要求 "Impersonate-User" 必须被设置。Impersonate-Extra-<附加名称>
:一个动态的头部字段,用来设置与用户相关的附加字段。
此字段可选;要求 "Impersonate-User" 被设置。为了能够以一致的形式保留,
<附加名称>
部分必须是小写字符,
如果有任何字符不是合法的 HTTP 头部标签字符,
则必须是 utf8 字符,且转换为百分号编码。Impersonate-Uid
:一个唯一标识符,用来表示所伪装的用户。此头部可选。
如果设置,则要求 "Impersonate-User" 也存在。Kubernetes 对此字符串没有格式要求。在 1.11.3 版本之前(以及 1.10.7、1.9.11),<附加名称>
只能包含合法的 HTTP 标签字符。
Impersonate-Uid
仅在 1.22.0 及更高版本中可用。
伪装带有用户组的用户时,所使用的伪装头部字段示例:
Impersonate-User: jane.doe@example.com
Impersonate-Group: developers
Impersonate-Group: admins
伪装带有 UID 和附加字段的用户时,所使用的伪装头部字段示例:
Impersonate-User: jane.doe@example.com
Impersonate-Extra-dn: cn=jane,ou=engineers,dc=example,dc=com
Impersonate-Extra-acme.com%2Fproject: some-project
Impersonate-Extra-scopes: view
Impersonate-Extra-scopes: development
Impersonate-Uid: 06f6ce97-e2c5-4ab8-7ba5-7654dd08d52b
在使用 kubectl
时,可以使用 --as
标志来配置 Impersonate-User
头部字段值,
使用 --as-group
标志配置 Impersonate-Group
头部字段值。
kubectl drain mynode
Error from server (Forbidden): User "clark" cannot get nodes at the cluster scope. (get nodes mynode)
设置 --as
和 --as-group
标志:
kubectl drain mynode --as=superman --as-group=system:masters
node/mynode cordoned
node/mynode drained
kubectl
不能对附加字段或 UID 执行伪装。
若要伪装成某个用户、某个组、用户标识符(UID))或者设置附加字段,
执行伪装操作的用户必须具有对所伪装的类别(user
、group
、uid
等)执行 impersonate
动词操作的能力。
对于启用了 RBAC 鉴权插件的集群,下面的 ClusterRole 封装了设置用户和组伪装字段所需的规则:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: impersonator
rules:
- apiGroups: [""]
resources: ["users", "groups", "serviceaccounts"]
verbs: ["impersonate"]
为了执行伪装,附加字段和所伪装的 UID 都位于 "authorization.k8s.io" apiGroup
中。
附加字段会被作为 userextras
资源的子资源来执行权限评估。
如果要允许用户为附加字段 “scopes” 和 UID 设置伪装头部,该用户需要被授予以下角色:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: scopes-and-uid-impersonator
rules:
# 可以设置 "Impersonate-Extra-scopes" 和 "Impersonate-Uid" 头部
- apiGroups: ["authentication.k8s.io"]
resources: ["userextras/scopes", "uids"]
verbs: ["impersonate"]
你也可以通过约束资源可能对应的 resourceNames
限制伪装头部的取值:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: limited-impersonator
rules:
# 可以伪装成用户 "jane.doe@example.com"
- apiGroups: [""]
resources: ["users"]
verbs: ["impersonate"]
resourceNames: ["jane.doe@example.com"]
# 可以伪装成用户组 "developers" 和 "admins"
- apiGroups: [""]
resources: ["groups"]
verbs: ["impersonate"]
resourceNames: ["developers","admins"]
# 可以将附加字段 "scopes" 伪装成 "view" 和 "development"
- apiGroups: ["authentication.k8s.io"]
resources: ["userextras/scopes"]
verbs: ["impersonate"]
resourceNames: ["view", "development"]
# 可以伪装 UID "06f6ce97-e2c5-4ab8-7ba5-7654dd08d52b"
- apiGroups: ["authentication.k8s.io"]
resources: ["uids"]
verbs: ["impersonate"]
resourceNames: ["06f6ce97-e2c5-4ab8-7ba5-7654dd08d52b"]
基于伪装成一个用户或用户组的能力,你可以执行任何操作,好像你就是那个用户或用户组一样。
出于这一原因,伪装操作是不受名字空间约束的。
如果你希望允许使用 Kubernetes RBAC 来执行身份伪装,就需要使用 ClusterRole
和 ClusterRoleBinding
,而不是 Role
或 RoleBinding
。
Kubernetes v1.22 [stable]
k8s.io/client-go
及使用它的工具(如 kubectl
和 kubelet
)
可以执行某个外部命令来获得用户的凭据信息。
这一特性的目的是便于客户端与 k8s.io/client-go
并不支持的身份认证协议
(LDAP、Kerberos、OAuth2、SAML 等)继承。
插件实现特定于协议的逻辑,之后返回不透明的凭据以供使用。
几乎所有的凭据插件使用场景中都需要在服务器端存在一个支持
Webhook 令牌身份认证组件的模块,
负责解析客户端插件所生成的凭据格式。
早期版本的 kubectl
内置了对 AKS 和 GKE 的认证支持,但这一功能已不再存在。
在一个假想的应用场景中,某组织运行这一个外部的服务,能够将特定用户的已签名的令牌转换成 LDAP 凭据。此服务还能够对 Webhook 令牌身份认证组件的请求做出响应以验证所提供的令牌。 用户需要在自己的工作站上安装一个凭据插件。
要对 API 服务器认证身份时:
kubectl
命令。TokenReview
请求。凭据插件通过 kubectl 配置文件 来作为 user 字段的一部分设置。
apiVersion: v1
kind: Config
users:
- name: my-user
user:
exec:
# 要执行的命令。必需。
command: "example-client-go-exec-plugin"
# 解析 ExecCredentials 资源时使用的 API 版本。必需。
# 插件返回的 API 版本必需与这里列出的版本匹配。
#
# 要与支持多个版本的工具(如 client.authentication.k8s.io/v1beta1)集成,
# 可以设置一个环境变量或者向工具传递一个参数标明 exec 插件所期望的版本,
# 或者从 KUBERNETES_EXEC_INFO 环境变量的 ExecCredential 对象中读取版本信息。
apiVersion: "client.authentication.k8s.io/v1"
# 执行此插件时要设置的环境变量。可选字段。
env:
- name: "FOO"
value: "bar"
# 执行插件时要传递的参数。可选字段。
args:
- "arg1"
- "arg2"
# 当可执行文件不存在时显示给用户的文本。可选的。
installHint: |
需要 example-client-go-exec-plugin 来在当前集群上执行身份认证。可以通过以下命令安装:
MacOS: brew install example-client-go-exec-plugin
Ubuntu: apt-get install example-client-go-exec-plugin
Fedora: dnf install example-client-go-exec-plugin
...
# 是否使用 KUBERNETES_EXEC_INFO 环境变量的一部分向这个 exec 插件
# 提供集群信息(可能包含非常大的 CA 数据)
provideClusterInfo: true
# Exec 插件与标准输入 I/O 数据流之间的协议。如果协议无法满足,
# 则插件无法运行并会返回错误信息。合法的值包括 "Never" (Exec 插件从不使用标准输入),
# "IfAvailable" (Exec 插件希望在可以的情况下使用标准输入),
# 或者 "Always" (Exec 插件需要使用标准输入才能工作)。必需字段。
interactiveMode: Never
clusters:
- name: my-cluster
cluster:
server: "https://172.17.4.100:6443"
certificate-authority: "/etc/kubernetes/ca.pem"
extensions:
- name: client.authentication.k8s.io/exec # 为每个集群 exec 配置保留的扩展名
extension:
arbitrary: config
this: 在设置 provideClusterInfo 时可通过环境变量 KUBERNETES_EXEC_INFO 指定
you: ["can", "put", "anything", "here"]
contexts:
- name: my-cluster
context:
cluster: my-cluster
user: my-user
current-context: my-cluster
apiVersion: v1
kind: Config
users:
- name: my-user
user:
exec:
# 要执行的命令。必需。
command: "example-client-go-exec-plugin"
# 解析 ExecCredentials 资源时使用的 API 版本。必需。
# 插件返回的 API 版本必需与这里列出的版本匹配。
#
# 要与支持多个版本的工具(如 client.authentication.k8s.io/v1)集成,
# 可以设置一个环境变量或者向工具传递一个参数标明 exec 插件所期望的版本,
# 或者从 KUBERNETES_EXEC_INFO 环境变量的 ExecCredential 对象中读取版本信息。
apiVersion: "client.authentication.k8s.io/v1beta1"
# 执行此插件时要设置的环境变量。可选字段。
env:
- name: "FOO"
value: "bar"
# 执行插件时要传递的参数。可选字段。
args:
- "arg1"
- "arg2"
# 当可执行文件不存在时显示给用户的文本。可选的。
installHint: |
需要 example-client-go-exec-plugin 来在当前集群上执行身份认证。可以通过以下命令安装:
MacOS: brew install example-client-go-exec-plugin
Ubuntu: apt-get install example-client-go-exec-plugin
Fedora: dnf install example-client-go-exec-plugin
...
# 是否使用 KUBERNETES_EXEC_INFO 环境变量的一部分向这个 exec 插件
# 提供集群信息(可能包含非常大的 CA 数据)
provideClusterInfo: true
# Exec 插件与标准输入 I/O 数据流之间的协议。如果协议无法满足,
# 则插件无法运行并会返回错误信息。合法的值包括 "Never"(Exec 插件从不使用标准输入),
# "IfAvailable" (Exec 插件希望在可以的情况下使用标准输入),
# 或者 "Always" (Exec 插件需要使用标准输入才能工作)。可选字段。
# 默认值为 "IfAvailable"。
interactiveMode: Never
clusters:
- name: my-cluster
cluster:
server: "https://172.17.4.100:6443"
certificate-authority: "/etc/kubernetes/ca.pem"
extensions:
- name: client.authentication.k8s.io/exec # 为每个集群 exec 配置保留的扩展名
extension:
arbitrary: config
this: 在设置 provideClusterInfo 时可通过环境变量 KUBERNETES_EXEC_INFO 指定
you: ["can", "put", "anything", "here"]
contexts:
- name: my-cluster
context:
cluster: my-cluster
user: my-user
current-context: my-cluster
解析相对命令路径时,kubectl 将其视为与配置文件比较而言的相对路径。
如果 KUBECONFIG 被设置为 /home/jane/kubeconfig
,而 exec 命令为
./bin/example-client-go-exec-plugin
,则要执行的可执行文件为
/home/jane/bin/example-client-go-exec-plugin
。
- name: my-user
user:
exec:
# 对 kubeconfig 目录而言的相对路径
command: "./bin/example-client-go-exec-plugin"
apiVersion: "client.authentication.k8s.io/v1"
interactiveMode: Never
所执行的命令会在 stdout
打印 ExecCredential
对象。
k8s.io/client-go
使用 status
中返回的凭据信息向 Kubernetes API 服务器执行身份认证。
所执行的命令会通过环境变量 KUBERNETES_EXEC_INFO
收到一个 ExecCredential
对象作为其输入。
此输入中包含类似于所返回的 ExecCredential
对象的预期 API 版本,
以及是否插件可以使用 stdin
与用户交互这类信息。
在交互式会话(即,某终端)中运行时,stdin
是直接暴露给插件使用的。
插件应该使用来自 KUBERNETES_EXEC_INFO
环境变量的 ExecCredential
输入对象中的 spec.interactive
字段来确定是否提供了 stdin
。
插件的 stdin
需求(即,为了能够让插件成功运行,是否 stdin
是可选的、
必须提供的或者从不会被使用的)是通过
kubeconfig
中的 user.exec.interactiveMode
来声明的(参见下面的表格了解合法值)。
字段 user.exec.interactiveMode
在 client.authentication.k8s.io/v1beta1
中是可选的,在 client.authentication.k8s.io/v1
中是必需的。
interactiveMode 取值 | 含义 |
---|---|
Never | 此 exec 插件从不需要使用标准输入,因此如论是否有标准输入提供给用户输入,该 exec 插件都能运行。 |
IfAvailable | 此 exec 插件希望在标准输入可用的情况下使用标准输入,但在标准输入不存在时也可运行。因此,无论是否存在给用户提供输入的标准输入,此 exec 插件都会运行。如果存在供用户输入的标准输入,则该标准输入会被提供给 exec 插件。 |
Always | 此 exec 插件需要标准输入才能正常运行,因此只有存在供用户输入的标准输入时,此 exec 插件才会运行。如果不存在供用户输入的标准输入,则 exec 插件无法运行,并且 exec 插件的执行者会因此返回错误信息。 |
与使用持有者令牌凭据,插件在 ExecCredential
的状态中返回一个令牌:
{
"apiVersion": "client.authentication.k8s.io/v1",
"kind": "ExecCredential",
"status": {
"token": "my-bearer-token"
}
}
{
"apiVersion": "client.authentication.k8s.io/v1beta1",
"kind": "ExecCredential",
"status": {
"token": "my-bearer-token"
}
}
另一种方案是,返回 PEM 编码的客户端证书和密钥,以便执行 TLS 客户端身份认证。
如果插件在后续调用中返回了不同的证书或密钥,k8s.io/client-go
会终止其与服务器的连接,从而强制执行新的 TLS 握手过程。
如果指定了这种方式,则 clientKeyData
和 clientCertificateData
字段都必须存在。
clientCertificateData
字段可能包含一些要发送给服务器的中间证书(Intermediate
Certificates)。
{
"apiVersion": "client.authentication.k8s.io/v1",
"kind": "ExecCredential",
"status": {
"clientCertificateData": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
"clientKeyData": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
}
}
{
"apiVersion": "client.authentication.k8s.io/v1beta1",
"kind": "ExecCredential",
"status": {
"clientCertificateData": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
"clientKeyData": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
}
}
作为一种可选方案,响应中还可以包含以 RFC 3339 时间戳格式给出的证书到期时间。 证书到期时间的有无会有如下影响:
{
"apiVersion": "client.authentication.k8s.io/v1",
"kind": "ExecCredential",
"status": {
"token": "my-bearer-token",
"expirationTimestamp": "2018-03-05T17:30:20-08:00"
}
}
{
"apiVersion": "client.authentication.k8s.io/v1beta1",
"kind": "ExecCredential",
"status": {
"token": "my-bearer-token",
"expirationTimestamp": "2018-03-05T17:30:20-08:00"
}
}
为了让 exec 插件能够获得特定与集群的信息,可以在
kubeconfig
中的 user.exec
设置 provideClusterInfo
。
这一特定于集群的信息就会通过 KUBERNETES_EXEC_INFO
环境变量传递给插件。
此环境变量中的信息可以用来执行特定于集群的凭据获取逻辑。
下面的 ExecCredential
清单描述的是一个示例集群信息。
{
"apiVersion": "client.authentication.k8s.io/v1",
"kind": "ExecCredential",
"spec": {
"cluster": {
"server": "https://172.17.4.100:6443",
"certificate-authority-data": "LS0t...",
"config": {
"arbitrary": "config",
"this": "可以在设置 provideClusterInfo 时通过 KUBERNETES_EXEC_INFO 环境变量提供",
"you": ["can", "put", "anything", "here"]
}
},
"interactive": true
}
}
{
"apiVersion": "client.authentication.k8s.io/v1beta1",
"kind": "ExecCredential",
"spec": {
"cluster": {
"server": "https://172.17.4.100:6443",
"certificate-authority-data": "LS0t...",
"config": {
"arbitrary": "config",
"this": "可以在设置 provideClusterInfo 时通过 KUBERNETES_EXEC_INFO 环境变量提供",
"you": ["can", "put", "anything", "here"]
}
},
"interactive": true
}
}
Kubernetes v1.28 [stable]
如果集群启用了此 API,你可以使用 SelfSubjectReview
API 来了解 Kubernetes
集群如何映射你的身份认证信息从而将你识别为某客户端。无论你是作为用户(通常代表一个真的人)还是作为
ServiceAccount 进行身份认证,这一 API 都可以使用。
SelfSubjectReview
对象没有任何可配置的字段。
Kubernetes API 服务器收到请求后,将使用用户属性填充 status 字段并将其返回给用户。
请求示例(主体将是 SelfSubjectReview
):
POST /apis/authentication.k8s.io/v1/selfsubjectreviews
{
"apiVersion": "authentication.k8s.io/v1",
"kind": "SelfSubjectReview"
}
响应示例:
{
"apiVersion": "authentication.k8s.io/v1",
"kind": "SelfSubjectReview",
"status": {
"userInfo": {
"name": "jane.doe",
"uid": "b6c7cfd4-f166-11ec-8ea0-0242ac120002",
"groups": [
"viewers",
"editors",
"system:authenticated"
],
"extra": {
"provider_id": ["token.company.example"]
}
}
}
}
为了方便,Kubernetes 提供了 kubectl auth whoami
命令。
执行此命令将产生以下输出(但将显示不同的用户属性):
简单的输出示例
ATTRIBUTE VALUE
Username jane.doe
Groups [system:authenticated]
包括额外属性的复杂示例
ATTRIBUTE VALUE
Username jane.doe
UID b79dbf30-0c6a-11ed-861d-0242ac120002
Groups [students teachers system:authenticated]
Extra: skills [reading learning]
Extra: subjects [math sports]
通过提供 output 标志,也可以打印结果的 JSON 或 YAML 表现形式:
{
"apiVersion": "authentication.k8s.io/v1alpha1",
"kind": "SelfSubjectReview",
"status": {
"userInfo": {
"username": "jane.doe",
"uid": "b79dbf30-0c6a-11ed-861d-0242ac120002",
"groups": [
"students",
"teachers",
"system:authenticated"
],
"extra": {
"skills": [
"reading",
"learning"
],
"subjects": [
"math",
"sports"
]
}
}
}
}
apiVersion: authentication.k8s.io/v1
kind: SelfSubjectReview
status:
userInfo:
username: jane.doe
uid: b79dbf30-0c6a-11ed-861d-0242ac120002
groups:
- students
- teachers
- system:authenticated
extra:
skills:
- reading
- learning
subjects:
- math
- sports
在 Kubernetes 集群中使用复杂的身份认证流程时,例如如果你使用 Webhook 令牌身份认证或 身份认证代理时, 此特性极其有用。
Kubernetes API 服务器在所有身份认证机制
(包括伪装),
被应用后填充 userInfo
,
如果你或某个身份认证代理使用伪装进行 SelfSubjectReview,你会看到被伪装用户的用户详情和属性。
默认情况下,所有经过身份认证的用户都可以在 APISelfSubjectReview
特性被启用时创建 SelfSubjectReview
对象。
这是 system:basic-user
集群角色允许的操作。
你只能在以下情况下进行 SelfSubjectReview
请求:
Kubernetes v1.18 [stable]
启动引导令牌是一种简单的持有者令牌(Bearer Token),这种令牌是在新建集群
或者在现有集群中添加新节点时使用的。
它被设计成能够支持 kubeadm
,
但是也可以被用在其他的案例中以便用户在不使用 kubeadm
的情况下启动集群。
它也被设计成可以通过 RBAC 策略,结合
kubelet TLS 启动引导
系统进行工作。
启动引导令牌被定义成一个特定类型的 Secret(bootstrap.kubernetes.io/token
),
并存在于 kube-system
名字空间中。
这些 Secret 会被 API 服务器上的启动引导认证组件(Bootstrap Authenticator)读取。
控制器管理器中的控制器 TokenCleaner 能够删除过期的令牌。
这些令牌也被用来在节点发现的过程中会使用的一个特殊的 ConfigMap 对象。
BootstrapSigner 控制器也会使用这一 ConfigMap。
启动引导令牌使用 abcdef.0123456789abcdef
的形式。
更加规范地说,它们必须符合正则表达式 [a-z0-9]{6}\.[a-z0-9]{16}
。
令牌的第一部分是 “Token ID”,它是一种公开信息,用于引用令牌并确保不会 泄露认证所使用的秘密信息。 第二部分是“令牌秘密(Token Secret)”,它应该被共享给受信的第三方。
启动引导令牌认证组件可以通过 API 服务器上的如下标志启用:
--enable-bootstrap-token-auth
启动引导令牌被启用后,可以作为持有者令牌的凭据,用于 API 服务器请求的身份认证。
Authorization: Bearer 07401b.f395accd246ae52d
令牌认证为用户名 system:bootstrap:<token id>
并且是组 system:bootstrappers
的成员。额外的组信息可以通过令牌的 Secret 来设置。
过期的令牌可以通过启用控制器管理器中的 tokencleaner
控制器来删除。
--controllers=*,tokencleaner
每个合法的令牌背后对应着 kube-system
名字空间中的某个 Secret 对象。
你可以从
这里
找到完整设计文档。
这是 Secret 看起来的样子。
apiVersion: v1
kind: Secret
metadata:
# name 必须是 "bootstrap-token-<token id>" 格式的
name: bootstrap-token-07401b
namespace: kube-system
# type 必须是 'bootstrap.kubernetes.io/token'
type: bootstrap.kubernetes.io/token
stringData:
# 供人阅读的描述,可选。
description: "The default bootstrap token generated by 'kubeadm init'."
# 令牌 ID 和秘密信息,必需。
token-id: 07401b
token-secret: f395accd246ae52d
# 可选的过期时间字段
expiration: 2017-03-10T03:22:11Z
# 允许的用法
usage-bootstrap-authentication: "true"
usage-bootstrap-signing: "true"
# 令牌要认证为的额外组,必须以 "system:bootstrappers:" 开头
auth-extra-groups: system:bootstrappers:worker,system:bootstrappers:ingress
Secret 的类型必须是 bootstrap.kubernetes.io/token
,而且名字必须是 bootstrap-token-<token id>
。
令牌必须存在于 kube-system
名字空间中。
usage-bootstrap-*
成员表明这个 Secret 的用途。启用时,值必须设置为 true
。
usage-bootstrap-authentication
表示令牌可以作为持有者令牌用于 API 服务器的身份认证。usage-bootstrap-signing
表示令牌可被用于 cluster-info
ConfigMap 的签名,
就像下面描述的那样。expiration
字段控制令牌的失效期。过期的令牌在用于身份认证时会被拒绝,在用于
ConfigMap 签名时会被忽略。
过期时间值是遵循 RFC3339
进行编码的 UTC 时间。启用 TokenCleaner 控制器会自动删除过期的令牌。
kubeadm
管理令牌你可以使用 kubeadm
工具管理运行中集群上的令牌。
参见 kubeadm token 文档
以了解详细信息。
除了身份认证,令牌还可以用于签名 ConfigMap。 这一用法发生在集群启动过程的早期,在客户端信任 API 服务器之前。 被签名的 ConfigMap 可以被共享令牌完成身份认证。
通过在控制器管理器上启用 bootstrapsigner
控制器可以启用 ConfigMap 签名特性。
--controllers=*,bootstrapsigner
被签名的 ConfigMap 是 kube-public
名字空间中的 cluster-info
。
典型的工作流中,客户端在未经认证和忽略 TLS 报错的状态下读取这个 ConfigMap。
通过检查 ConfigMap 中嵌入的签名校验 ConfigMap 的载荷。
ConfigMap 会是这个样子的:
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-info
namespace: kube-public
data:
jws-kubeconfig-07401b: eyJhbGciOiJIUzI1NiIsImtpZCI6IjA3NDAxYiJ9..tYEfbo6zDNo40MQE07aZcQX2m3EB2rO3NuXtxVMYm9U
kubeconfig: |
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: <非常长的证书数据>
server: https://10.138.0.2:6443
name: ""
contexts: []
current-context: ""
kind: Config
preferences: {}
users: []
ConfigMap 的 kubeconfig
成员是一个填好了集群信息的配置文件。
这里主要交换的信息是 certificate-authority-data
。在将来可能会有扩展。
签名是一个使用 “detached” 模式生成的 JWS 签名。
为了检验签名,用户应该按照 JWS 规则(base64 编码且丢掉结尾的 =
)对
kubeconfig
的载荷进行编码。完成编码的载荷会被插入到两个句点中间,形成完整的
JWS。你可以使用完整的令牌(比如 07401b.f395accd246ae52d
)作为共享密钥,
通过 HS256
方式 (HMAC-SHA256) 对 JWS 进行校验。
用户必须确保使用了 HS256。
任何拥有了启动引导令牌的主体都可以为该令牌生成一个合法的签名。 当使用 ConfigMap 签名时,非常不建议针对很多客户使用相同的令牌,因为某个被攻击的 客户可能对另一个一来签名来开启 TLS 信任的客户发起中间人攻击。
参考 kubeadm 实现细节 了解更多信息。
Kubernetes 鉴权在身份认证之后进行。 通常,发出请求的客户端必须经过身份验证(登录)才能允许其请求; 但是,Kubernetes 在某些情况下也允许匿名请求。
有关鉴权在 API 访问控制中的位置这类进一步的语境信息, 请阅读控制对 Kubernetes API 的访问。
Kubernetes 对 API 请求的鉴权在 API 服务器内进行。 API 服务器根据所有策略评估所有请求属性,可能还会咨询外部服务,然后允许或拒绝该请求。
API 请求的所有部分都必须通过某种鉴权机制才能继续, 换句话说:默认情况下拒绝访问。
当系统配置了多个鉴权模块时,Kubernetes 将按顺序使用每个模块。 如果任何鉴权模块批准或拒绝请求,则立即返回该决定,并且不会与其他鉴权模块协商。 如果所有模块对请求没有意见,则拒绝该请求。 总体拒绝裁决意味着 API 服务器拒绝请求并以 HTTP 403(禁止)状态进行响应。
Kubernetes 仅审查以下 API 请求属性:
user
字符串。/api
或 /healthz
。get
、list
、create
、update
、patch
、watch
、
proxy
、redirect
、delete
和 deletecollection
用于资源请求。
要确定资源 API 端点的请求动词,请参阅请求动词和鉴权。get
、post
、put
和 delete
用于非资源请求。get
、update
、patch
和 delete
动词的资源请求,你必须提供资源名称。对于 /api/v1/...
或 /apis/<group>/<version>/...
之外的端点的请求被视为非资源请求(Non-Resource Requests),
并使用该请求的 HTTP 方法的小写形式作为其请求动词。
例如,对 /api
或 /healthz
这类端点的 GET
请求将使用 get 作为其动词。
为了确定资源 API 端点的请求动词,Kubernetes 会映射所使用的 HTTP 动词, 并考虑该请求是否作用于单个资源或资源集合:
HTTP 动词 | 请求动词 |
---|---|
POST | create |
GET 、HEAD | get(针对单个资源)、list(针对集合,包括完整的对象内容)、watch(用于查看单个资源或资源集合) |
PUT | update |
PATCH | patch |
DELETE | delete(针对单个资源)、deletecollection(针对集合) |
get、list 和 watch 动作都可以返回一个资源的完整详细信息。就返回的数据而言,它们是等价的。 例如,对 secrets 使用 list 仍然会显示所有已返回资源的 data 属性。
Kubernetes 有时使用专门的动词以对额外的权限进行鉴权。例如:
users
、groups
和 serviceaccounts
以及 authentication.k8s.io
API 组中的 userextras
所使用的 impersonate 动词。rbac.authorization.k8s.io
API 组中 roles
和 clusterroles
资源的 bind
和 escalate 动词Kubernetes 需要 REST API 请求所共有的属性, 这意味着 Kubernetes 鉴权可与现有的组织范围或云提供商范围的访问控制系统配合使用, 这些系统可以处理除 Kubernetes API 之外的其他 API。
AlwaysAllow
AlwaysDeny
ABAC
(基于属性的访问控制)RBAC
(基于角色的访问控制)rbac.authorization.k8s.io
API 组来驱动鉴权决策,
允许你通过 Kubernetes API 动态配置权限策略。Node
Webhook
启用 AlwaysAllow
模式会绕过鉴权;请勿在你不信任所有潜在 API
客户端(包括你运行的工作负载)的集群上使用该模式。
鉴权机制通常返回“拒绝”或“无意见”的结果;
有关更多信息,请参阅鉴权裁决。
激活 AlwaysAllow
意味着如果所有其他鉴权组件都返回“无意见”,则允许该请求。
例如,--authorization-mode=AlwaysAllow,RBAC
与 --authorization-mode=AlwaysAllow
具有相同的效果,因为 Kubernetes RBAC 不提供否定(拒绝)访问规则。
你不应在可从公共互联网访问 API 服务器的 Kubernetes 集群上使用 AlwaysAllow
模式。
system:masters
组system:masters
组是 Kubernetes 内置的一个组,授予其成员对 API 服务器的无限制访问权限。
任何被分配到此组的用户都具有完全的集群管理员权限,可以绕过由 RBAC 或 Webhook 机制施加的任何鉴权限制。
请避免将用户添加到此组。
如果你确实需要授予某个用户集群管理员权限,可以通过创建一个
ClusterRoleBinding
将其绑定到内置的 cluster-admin
ClusterRole。
你可以仅使用命令行参数, 或使用配置文件来配置 Kubernetes API 服务器的鉴权链,后者目前是 Beta 特性。
你必须选择两种配置方法之一;不允许同时设置 --authorization-config
路径并使用
--authorization-mode
和 --authorization-webhook-*
命令行参数配置鉴权 Webhook。
如果你尝试这样做,API 服务器会在启动期间报告错误消息,然后立即退出。
Kubernetes v1.8 [stable]
你可以使用以下模式:
--authorization-mode=ABAC
(基于属性的访问控制模式)--authorization-mode=RBAC
(基于角色的访问控制模式)--authorization-mode=Node
(节点鉴权组件)--authorization-mode=Webhook
(Webhook 鉴权模式)--authorization-mode=AlwaysAllow
(始终允许请求;存在安全风险)--authorization-mode=AlwaysDeny
(始终拒绝请求)你可以选择多种鉴权模式;例如:--authorization-mode=Node,Webhook
Kubernetes 根据你在 API 服务器的命令行上指定鉴权模块的顺序来检查鉴权模块, 因此较早的模块具有更高的优先级来允许或拒绝请求。
你不能将 --authorization-mode
命令行参数与用于使用本地文件配置鉴权的
--authorization-config
命令行参数结合使用。
有关 API 服务器命令行参数的更多信息,请阅读
kube-apiserver
参考。
Kubernetes v1.30 [beta]
(enabled by default: true)作为一项 Beta 级别特性,Kubernetes 允许你配置可包含多个 Webhook 的鉴权链。 该链中的鉴权项可以具有明确定义的参数,这些参数可以按特定顺序检查请求, 从而为你提供细粒度的控制,例如在失败时明确拒绝。
配置文件方法甚至允许你指定 CEL规则, 在将请求发送到 Webhook 之前对其进行预过滤,从而帮助你防止不必要的调用。 修改配置文件时,API 服务器还会自动重新加载鉴权链。
你可以使用 --authorization-config
命令行参数指定鉴权配置的路径。
如果你想使用命令行参数而不是配置文件,这也是一种有效且受支持的方法。 某些鉴权功能(例如:多个 Webhook、Webhook 失败策略和预过滤规则)仅在使用鉴权配置文件时可用。
---
#
# 请勿按原样使用配置,这只是一个示例。
#
apiVersion: apiserver.config.k8s.io/v1beta1
kind: AuthorizationConfiguration
authorizers:
- type: Webhook
# 用于描述鉴权人的名称
# 这明确用于监控机制的指标
# 注意
# - 该字段的验证与今天的 K8s 标签的验证方式类似。
# 必填,无默认值
name: webhook
webhook:
# 缓存来自 Webhook 鉴权组件的“鉴权”响应的持续时间
# 与设置 `--authorization-webhook-cache-authorized-ttl` 标志相同
# 默认值:5m0s
authorizedTTL: 30s
# 缓存来自 Webhook 鉴权组件的“未授权”响应的持续时间。
# 与设置 `--authorization-webhook-cache-unauthorized-ttl` 标志相同
# 默认值:30s
unauthorizedTTL: 30s
# Webhook 请求超时
# 允许的最大时间为 30 秒。
# 必填,没有默认值。
timeout: 3s
# 要发送到 Webhook 并期望从 webhook 获得的 authorization.k8s.io SubjectAccessReview 的 API 版本。
# 与设置 `--authorization-webhook-version` 标志相同
# 必填,无默认值
# 有效值:v1beta1、v1
subjectAccessReviewVersion: v1
# MatchConditionSubjectAccessReviewVersion 指定评估 CEL 表达式的 SubjectAccessReview 版本
# 有效值:v1
# 必填,无默认值
matchConditionSubjectAccessReviewVersion: v1
# 当 Webhook 请求无法完成或返回格式错误的响应或评估 matchConditions 时出现错误时,控制鉴权决定。
# 有效值:
# - NoOpinion:继续联系后续鉴权组件,看其中是否有人允许该请求
# - Deny:拒绝请求而不咨询后续鉴权组件
# 必填,没有默认值。
failurePolicy: Deny
connectionInfo:
# 控制 Webhook 如何与服务器通信。
# 有效值:
# - KubeConfigFile:使用 kubeConfigFile 中指定的文件来定位服务器。
# - InClusterConfig:使用集群内配置来调用由 kube-apiserver 托管的 SubjectAccessReview API,kube-apiserver 不允许使用此模式。
type: KubeConfigFile
# 连接信息的 KubeConfig 文件的路径
# 如果 connectionInfo.Type 是 KubeConfig,则为必填项
kubeConfigFile: /kube-system-authz-webhook.yaml
# matchConditions 是将请求发送到此 Webhook 必须满足的条件列表。
# matchConditions 为空列表表示匹配所有请求。
# 最多允许 64 个匹配条件。
#
# 精确匹配逻辑如下(按顺序):
# 1. 如果至少一个 matchCondition 计算结果为 FALSE,则跳过 Webhook。
# 2. 如果所有 matchConditions 计算结果为 TRUE,则调用 Webhook。
# 3. 如果至少一个 matchCondition 计算结果为错误(但没有一个为 FALSE):
# - 如果 FailurePolicy=Deny,则 Webhook 拒绝请求
# - 如果 FailurePolicy=NoOpinion,则忽略错误并跳过 Webhook
matchConditions:
# 表达式表示将由 CEL 评估的表达式。必须评估为布尔值。
# CEL 表达式可以访问 v1 版本中的 SubjectAccessReview 的内容。
# 如果请求变量中 subjectAccessReviewVersion 指定的版本是 v1beta1,
# 在评估 CEL 表达式之前,内容将转换为 v1 版本。
#
# CEL 文档:https://kubernetes.io/docs/reference/using-api/cel/
#
# 仅向 Webhook 发送资源请求
- expression: has(request.resourceAttributes)
# 仅拦截对 kube-system 的请求
- expression: request.resourceAttributes.namespace == 'kube-system'
# 不要拦截来自 kube-system 服务账户的请求
- expression: "!('system:serviceaccounts:kube-system' in request.groups)"
- type: Node
name: node
- type: RBAC
name: rbac
- type: Webhook
name: in-cluster-authorizer
webhook:
authorizedTTL: 5m
unauthorizedTTL: 30s
timeout: 3s
subjectAccessReviewVersion: v1
failurePolicy: NoOpinion
connectionInfo:
type: InClusterConfig
使用配置文件配置鉴权链时,请确保所有控制平面节点具有相同的文件内容。升级/降级集群时,请记下 API 服务器配置。 例如,如果从 Kubernetes 1.30 升级到 Kubernetes 1.31,则需要确保配置文件的格式是 Kubernetes 1.31 可以理解的,然后再升级集群。 如果降级到 1.30,则需要适当设置配置。
当 API 服务器观察到文件的更改时,Kubernetes 会重新加载鉴权配置文件, 如果没有观察到更改事件,则也会按照 60 秒的计划重新加载。
你必须确保重新加载时文件中所有非 Webhook 鉴权组件类型保持不变。
重新加载不能添加或删除节点或 RBAC 鉴权组件(可以重新排序,但不能添加或删除)。
能够直接或通过启用间接工作负载管理的对象在命名空间中创建/编辑 Pod 的用户可能能够在该命名空间中提升其权限。 权限提升的潜在途径包括 Kubernetes API 扩展及其相关的 控制器。
作为集群管理员,授予创建或编辑工作负载的访问权限时请务必小心谨慎。 权限提升路径中记录了有关滥用这些内容的一些细节。
如果你允许攻击者或不值得信任的用户在该命名空间中运行任意 Pod, 则他们可以通过不同的方式在命名空间内获得额外的权限:
作为系统管理员,在部署允许用户更改上述区域的 CustomResourceDefinitions 时应谨慎行事,这些可能会打开特权升级路径。 在配置你的鉴权控制时,请考虑这种变化的后果。
kubectl
提供 auth can-i
子命令,用于快速查询 API 鉴权。
该命令使用 SelfSubjectAccessReview
API 来确定当前用户是否可以执行给定操作,
无论使用何种鉴权模式该命令都可以工作。
kubectl auth can-i create deployments --namespace dev
输出类似于:
yes
kubectl auth can-i create deployments --namespace prod
输出类似于:
no
管理员可以将此与用户扮演(User Impersonation) 结合使用,以确定其他用户可以执行的操作。
kubectl auth can-i list secrets --namespace dev --as dave
输出类似于:
no
类似地,检查名字空间 dev
里的 dev-sa
服务账户是否可以列举名字空间 target
里的 Pod:
kubectl auth can-i list pods \
--namespace target \
--as system:serviceaccount:dev:dev-sa
输出类似于:
yes
SelfSubjectAccessReview 是 authorization.k8s.io
API 组的一部分,它将 API
服务器鉴权公开给外部服务。该组中的其他资源包括:
SubjectAccessReview
: 对任意用户的访问进行评估,而不仅仅是当前用户。
当鉴权决策被委派给 API 服务器时很有用。例如,kubelet 和扩展 API
服务器使用它来确定用户对自己的 API 的访问权限。
LocalSubjectAccessReview
: 与 SubjectAccessReview
类似,但仅限于特定的名字空间。
SelfSubjectRulesReview
: 返回用户可在名字空间内执行的操作集的审阅。
用户可以快速汇总自己的访问权限,或者用于 UI 中的隐藏/显示动作。
可以通过创建普通的 Kubernetes 资源来查询这些 API,其中返回对象的响应 status
字段是查询的结果,例如:
kubectl create -f - -o yaml << EOF
apiVersion: authorization.k8s.io/v1
kind: SelfSubjectAccessReview
spec:
resourceAttributes:
group: apps
name: deployments
verb: create
namespace: dev
EOF
生成的 SelfSubjectAccessReview 类似于:
apiVersion: authorization.k8s.io/v1
kind: SelfSubjectAccessReview
metadata:
creationTimestamp: null
spec:
resourceAttributes:
group: apps
name: deployments
namespace: dev
verb: create
status:
allowed: true
denied: false
基于角色(Role)的访问控制(RBAC)是一种基于组织中用户的角色来调节控制对计算机或网络资源的访问的方法。
RBAC 鉴权机制使用 rbac.authorization.k8s.io
API 组来驱动鉴权决定,
允许你通过 Kubernetes API 动态配置策略。
要启用 RBAC,在启动 API 服务器时将
--authorization-mode
参数设置为一个逗号分隔的列表并确保其中包含 RBAC
。
kube-apiserver --authorization-mode=Example,RBAC --<其他选项> --<其他选项>
RBAC API 声明了四种 Kubernetes 对象:Role、ClusterRole、RoleBinding 和
ClusterRoleBinding。你可以像使用其他 Kubernetes 对象一样,
通过类似 kubectl
这类工具描述或修补 RBAC
对象。
这些对象在设计时即实施了一些访问限制。如果你在学习过程中对集群做了更改, 请参考避免特权提升和引导一节, 以了解这些限制会以怎样的方式阻止你做出修改。
RBAC 的 Role 或 ClusterRole 中包含一组代表相关权限的规则。 这些权限是纯粹累加的(不存在拒绝某操作的规则)。
Role 总是用来在某个名字空间内设置访问权限; 在你创建 Role 时,你必须指定该 Role 所属的名字空间。
与之相对,ClusterRole 则是一个集群作用域的资源。这两种资源的名字不同(Role 和 ClusterRole) 是因为 Kubernetes 对象要么是名字空间作用域的,要么是集群作用域的,不可两者兼具。
ClusterRole 有若干用法。你可以用它来:
如果你希望在名字空间内定义角色,应该使用 Role; 如果你希望定义集群范围的角色,应该使用 ClusterRole。
下面是一个位于 "default" 名字空间的 Role 的示例,可用来授予对 Pod 的读访问权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 标明 core API 组
resources: ["pods"]
verbs: ["get", "watch", "list"]
ClusterRole 同样可以用于授予 Role 能够授予的权限。 因为 ClusterRole 属于集群范围,所以它也可以为以下资源授予访问权限:
集群范围资源(比如节点(Node))
非资源端点(比如 /healthz
)
跨名字空间访问的名字空间作用域的资源(如 Pod)
比如,你可以使用 ClusterRole 来允许某特定用户执行 kubectl get pods --all-namespaces
下面是一个 ClusterRole 的示例,可用来为任一特定名字空间中的 Secret 授予读访问权限, 或者跨名字空间的访问权限(取决于该角色是如何绑定的):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" 被忽略,因为 ClusterRoles 不受名字空间限制
name: secret-reader
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 Secret 资源的名称为 "secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]
Role 或 ClusterRole 对象的名称必须是合法的路径分段名称。
角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户。 它包含若干主体(Subject)(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。 RoleBinding 在指定的名字空间中执行授权,而 ClusterRoleBinding 在集群范围执行授权。
一个 RoleBinding 可以引用同一的名字空间中的任何 Role。 或者,一个 RoleBinding 可以引用某 ClusterRole 并将该 ClusterRole 绑定到 RoleBinding 所在的名字空间。 如果你希望将某 ClusterRole 绑定到集群中所有名字空间,你要使用 ClusterRoleBinding。
RoleBinding 或 ClusterRoleBinding 对象的名称必须是合法的 路径分段名称。
下面的例子中的 RoleBinding 将 "pod-reader" Role 授予在 "default" 名字空间中的用户 "jane"。 这样,用户 "jane" 就具有了读取 "default" 名字空间中所有 Pod 的权限。
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定允许 "jane" 读取 "default" 名字空间中的 Pod
# 你需要在该名字空间中有一个名为 “pod-reader” 的 Role
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# 你可以指定不止一个“subject(主体)”
- kind: User
name: jane # "name" 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" 指定与某 Role 或 ClusterRole 的绑定关系
kind: Role # 此字段必须是 Role 或 ClusterRole
name: pod-reader # 此字段必须与你要绑定的 Role 或 ClusterRole 的名称匹配
apiGroup: rbac.authorization.k8s.io
RoleBinding 也可以引用 ClusterRole,以将对应 ClusterRole 中定义的访问权限授予 RoleBinding 所在名字空间的资源。这种引用使得你可以跨整个集群定义一组通用的角色, 之后在多个名字空间中复用。
例如,尽管下面的 RoleBinding 引用的是一个 ClusterRole,"dave"(这里的主体, 区分大小写)只能访问 "development" 名字空间中的 Secret 对象,因为 RoleBinding 所在的名字空间(由其 metadata 决定)是 "development"。
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定使得用户 "dave" 能够读取 "development" 名字空间中的 Secret
# 你需要一个名为 "secret-reader" 的 ClusterRole
kind: RoleBinding
metadata:
name: read-secrets
# RoleBinding 的名字空间决定了访问权限的授予范围。
# 这里隐含授权仅在 "development" 名字空间内的访问权限。
namespace: development
subjects:
- kind: User
name: dave # 'name' 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
要跨整个集群完成访问权限的授予,你可以使用一个 ClusterRoleBinding。 下面的 ClusterRoleBinding 允许 "manager" 组内的所有用户访问任何名字空间中的 Secret。
apiVersion: rbac.authorization.k8s.io/v1
# 此集群角色绑定允许 “manager” 组中的任何人访问任何名字空间中的 Secret 资源
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager # 'name' 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
创建了绑定之后,你不能再修改绑定对象所引用的 Role 或 ClusterRole。
试图改变绑定对象的 roleRef
将导致合法性检查错误。
如果你想要改变现有绑定对象中 roleRef
字段的内容,必须删除重新创建绑定对象。
这种限制有两个主要原因:
roleRef
设置为不可以改变,这使得可以为用户授予对现有绑定对象的 update
权限,
这样可以让他们管理主体列表,同时不能更改被授予这些主体的角色。roleRef
,
这样可以确保要赋予绑定的所有主体会被授予新的角色(而不是在允许或者不小心修改了
roleRef
的情况下导致所有现有主体未经验证即被授予新角色对应的权限)。命令 kubectl auth reconcile
可以创建或者更新包含 RBAC 对象的清单文件,
并且在必要的情况下删除和重新创建绑定对象,以改变所引用的角色。
更多相关信息请参照命令用法和示例。
在 Kubernetes API 中,大多数资源都是使用对象名称的字符串表示来呈现与访问的。 例如,对于 Pod 应使用 "pods"。 RBAC 使用对应 API 端点的 URL 中呈现的名字来引用资源。 有一些 Kubernetes API 涉及子资源(subresource),例如 Pod 的日志。 对 Pod 日志的请求看起来像这样:
GET /api/v1/namespaces/{namespace}/pods/{name}/log
在这里,pods
对应名字空间作用域的 Pod 资源,而 log
是 pods
的子资源。
在 RBAC 角色表达子资源时,使用斜线(/
)来分隔资源和子资源。
要允许某主体读取 pods
同时访问这些 Pod 的 log
子资源,你可以这样写:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
对于某些请求,也可以通过 resourceNames
列表按名称引用资源。
在指定时,可以将请求限定为资源的单个实例。
下面的例子中限制可以 get
和 update
一个名为 my-configmap
的
ConfigMap:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: configmap-updater
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 ConfigMap 资源的名称为 "configmaps"
resources: ["configmaps"]
resourceNames: ["my-configmap"]
verbs: ["update", "get"]
你不能使用资源名字来限制 create
或者 deletecollection
请求。
对于 create
请求而言,这是因为在鉴权时可能还不知道新对象的名字。
如果你使用 resourceName
来限制 list
或者 watch
请求,
客户端必须在它们的 list
或者 watch
请求里包含一个与指定的 resourceName
匹配的 metadata.name
字段选择器。
例如,kubectl get configmaps --field-selector=metadata.name=my-configmap
你可以使用通配符 *
批量引用所有的 resources
、apiGroups
和 verbs
对象,无需逐一引用。
对于 nonResourceURLs
,你可以将通配符 *
作为后缀实现全局通配,
对于 resourceNames
,空集表示没有任何限制。
下面的示例对 example.com
API 组中所有当前和未来资源执行所有动作。
这类似于内置的 cluster-admin
。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: example.com-superuser # 此角色仅作示范,请勿使用
rules:
- apiGroups: ["example.com"]
resources: ["*"]
verbs: ["*"]
在 resources 和 verbs 条目中使用通配符会为敏感资源授予过多的访问权限。 例如,如果添加了新的资源类型、新的子资源或新的自定义动词, 通配符条目会自动授予访问权限,用户可能不希望出现这种情况。 应该执行最小特权原则, 使用具体的 resources 和 verbs 确保仅赋予工作负载正常运行所需的权限。
你可以将若干 ClusterRole 聚合(Aggregate) 起来,形成一个复合的 ClusterRole。
作为集群控制面的一部分,控制器会监视带有 aggregationRule
的 ClusterRole 对象集合。aggregationRule
为控制器定义一个标签选择算符供后者匹配应该组合到当前
ClusterRole 的 roles
字段中的 ClusterRole 对象。
控制平面会覆盖你在聚合 ClusterRole 的 rules
字段中手动指定的所有值。
如果你想更改或添加规则,请在被 aggregationRule
所选中的 ClusterRole
对象上执行变更。
下面是一个聚合 ClusterRole 的示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # 控制面自动填充这里的规则
如果你创建一个与某个已存在的聚合 ClusterRole 的标签选择算符匹配的 ClusterRole,
这一变化会触发新的规则被添加到聚合 ClusterRole 的操作。
下面的例子中,通过创建一个标签同样为 rbac.example.com/aggregate-to-monitoring: true
的 ClusterRole,新的规则可被添加到 "monitoring" ClusterRole 中。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-endpoints
labels:
rbac.example.com/aggregate-to-monitoring: "true"
# 当你创建 "monitoring-endpoints" ClusterRole 时,
# 下面的规则会被添加到 "monitoring" ClusterRole 中
rules:
- apiGroups: [""]
resources: ["services", "endpointslices", "pods"]
verbs: ["get", "list", "watch"]
默认的面向用户的角色使用 ClusterRole 聚合。 这使得作为集群管理员的你可以为扩展默认规则,包括为定制资源设置规则, 比如通过 CustomResourceDefinitions 或聚合 API 服务器提供的定制资源。
例如,下面的 ClusterRoles 让默认角色 "admin" 和 "edit" 拥有管理自定义资源 "CronTabs" 的权限,
"view" 角色对 CronTab 资源拥有读操作权限。
你可以假定 CronTab 对象在 API 服务器所看到的 URL 中被命名为 "crontabs"
。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: aggregate-cron-tabs-edit
labels:
# 添加以下权限到默认角色 "admin" 和 "edit" 中
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
- apiGroups: ["stable.example.com"]
resources: ["crontabs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: aggregate-cron-tabs-view
labels:
# 添加以下权限到 "view" 默认角色中
rbac.authorization.k8s.io/aggregate-to-view: "true"
rules:
- apiGroups: ["stable.example.com"]
resources: ["crontabs"]
verbs: ["get", "list", "watch"]
以下示例均为从 Role 或 ClusterRole 对象中截取出来,我们仅展示其 rules
部分。
允许读取在核心 API 组下的 "pods"
:
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 Pod 资源的名称为 "pods"
resources: ["pods"]
verbs: ["get", "list", "watch"]
允许在 "apps"
API 组中读/写 Deployment(在 HTTP 层面,对应 URL
中资源部分为 "deployments"
):
rules:
- apiGroups: ["apps"]
#
# 在 HTTP 层面,用来访问 Deployment 资源的名称为 "deployments"
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
允许读取核心 API 组中的 Pod 和读/写 "batch"
API 组中的 Job 资源:
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 Pod 资源的名称为 "pods"
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
# 在 HTTP 层面,用来访问 Job 资源的名称为 "jobs"
resources: ["jobs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
允许读取名称为 "my-config" 的 ConfigMap(需要通过 RoleBinding 绑定以限制为某名字空间中特定的 ConfigMap):
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 ConfigMap 资源的名称为 "configmaps"
resources: ["configmaps"]
resourceNames: ["my-config"]
verbs: ["get"]
允许读取在核心组中的 "nodes"
资源(因为 Node
是集群作用域的,所以需要
ClusterRole 绑定到 ClusterRoleBinding 才生效):
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 Node 资源的名称为 "nodes"
resources: ["nodes"]
verbs: ["get", "list", "watch"]
允许针对非资源端点 /healthz
和其子路径上发起 GET 和 POST 请求
(必须在 ClusterRole 绑定 ClusterRoleBinding 才生效):
rules:
- nonResourceURLs: ["/healthz", "/healthz/*"] # nonResourceURL 中的 '*' 是一个全局通配符
verbs: ["get", "post"]
RoleBinding 或者 ClusterRoleBinding 可绑定角色到某主体(Subject) 上。 主体可以是组,用户或者服务账户。
Kubernetes 用字符串来表示用户名。 用户名可以是普通的用户名,像 "alice";或者是邮件风格的名称,如 "bob@example.com", 或者是以字符串形式表达的数字 ID。你作为 Kubernetes 管理员负责配置身份认证模块, 以便后者能够生成你所期望的格式的用户名。
前缀 system:
是 Kubernetes 系统保留的,所以你要确保所配置的用户名或者组名不能出现上述
system:
前缀。除了对前缀的限制之外,RBAC 鉴权系统不对用户名格式作任何要求。
在 Kubernetes 中,身份认证(Authenticator)模块提供用户组信息。
与用户名一样,用户组名也用字符串来表示,而且对该字符串没有格式要求,
只是不能使用保留的前缀 system:
。
服务账户(ServiceAccount)
的用户名前缀为 system:serviceaccount:
,属于前缀为 system:serviceaccounts:
的用户组。
system:serviceaccount:
(单数)是用于服务账户用户名的前缀;system:serviceaccounts:
(复数)是用于服务账户组名的前缀。下面示例是 RoleBinding
中的片段,仅展示其 subjects
的部分。
对于名称为 alice@example.com
的用户:
subjects:
- kind: User
name: "alice@example.com"
apiGroup: rbac.authorization.k8s.io
对于名称为 frontend-admins
的用户组:
subjects:
- kind: Group
name: "frontend-admins"
apiGroup: rbac.authorization.k8s.io
对于 kube-system
名字空间中的默认服务账户:
subjects:
- kind: ServiceAccount
name: default
namespace: kube-system
对于 "qa" 名称空间中的所有服务账户:
subjects:
- kind: Group
name: system:serviceaccounts:qa
apiGroup: rbac.authorization.k8s.io
对于在任何名字空间中的服务账户:
subjects:
- kind: Group
name: system:serviceaccounts
apiGroup: rbac.authorization.k8s.io
对于所有已经过身份认证的用户:
subjects:
- kind: Group
name: system:authenticated
apiGroup: rbac.authorization.k8s.io
对于所有未通过身份认证的用户:
subjects:
- kind: Group
name: system:unauthenticated
apiGroup: rbac.authorization.k8s.io
对于所有用户:
subjects:
- kind: Group
name: system:authenticated
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: system:unauthenticated
apiGroup: rbac.authorization.k8s.io
API 服务器创建一组默认的 ClusterRole 和 ClusterRoleBinding 对象。
这其中许多是以 system:
为前缀的,用以标识对应资源是直接由集群控制面管理的。
所有的默认 ClusterRole 和 ClusterRoleBinding 都有
kubernetes.io/bootstrapping=rbac-defaults
标签。
在修改名称包含 system:
前缀的 ClusterRole 和 ClusterRoleBinding
时要格外小心。
对这些资源的更改可能导致集群无法正常运作。
在每次启动时,API 服务器都会更新默认 ClusterRole 以添加缺失的各种权限, 并更新默认的 ClusterRoleBinding 以增加缺失的各类主体。 这种自动协商机制允许集群去修复一些不小心发生的修改, 并且有助于保证角色和角色绑定在新的发行版本中有权限或主体变更时仍然保持最新。
如果要禁止此功能,请将默认 ClusterRole 以及默认 ClusterRoleBinding 的
rbac.authorization.kubernetes.io/autoupdate
注解设置成 false
。
注意,缺少默认权限和角色绑定主体可能会导致集群无法正常工作。
如果基于 RBAC 的鉴权机制被启用,则自动协商功能默认是被启用的。
无论是经过身份验证的还是未经过身份验证的用户,
默认的集群角色绑定都授权他们读取被认为是可安全地公开访问的 API(包括 CustomResourceDefinitions)。
如果要禁用匿名的未经过身份验证的用户访问,请在 API 服务器配置中添加
--anonymous-auth=false
的配置选项。
通过运行命令 kubectl
可以查看这些角色的配置信息:
kubectl get clusterroles system:discovery -o yaml
如果你编辑该 ClusterRole,你所作的变更会被 API 服务器在重启时自动覆盖, 这是通过自动协商机制完成的。要避免这类覆盖操作, 要么不要手动编辑这些角色,要么禁止自动协商机制。
默认 ClusterRole | 默认 ClusterRoleBinding | 描述 |
---|---|---|
system:basic-user | system:authenticated 组 | 允许用户以只读的方式去访问他们自己的基本信息。在 v1.14 版本之前,这个角色在默认情况下也绑定在 system:unauthenticated 上。 |
system:discovery | system:authenticated 组 | 允许以只读方式访问 API 发现端点,这些端点用来发现和协商 API 级别。 在 v1.14 版本之前,这个角色在默认情况下绑定在 system:unauthenticated 上。 |
system:public-info-viewer | system:authenticated 和 system:unauthenticated 组 | 允许对集群的非敏感信息进行只读访问,此角色是在 v1.14 版本中引入的。 |
一些默认的 ClusterRole 不是以前缀 system:
开头的。这些是面向用户的角色。
它们包括超级用户(Super-User)角色(cluster-admin
)、
使用 ClusterRoleBinding 在集群范围内完成授权的角色(cluster-status
)、
以及使用 RoleBinding 在特定名字空间中授予的角色(admin
、edit
、view
)。
面向用户的 ClusterRole 使用 ClusterRole 聚合以允许管理员在这些
ClusterRole 上添加用于定制资源的规则。如果想要添加规则到 admin
、edit
或者 view
,
可以创建带有以下一个或多个标签的 ClusterRole:
metadata:
labels:
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
默认 ClusterRole | 默认 ClusterRoleBinding | 描述 |
---|---|---|
cluster-admin | system:masters 组 | 允许超级用户在平台上的任何资源上执行所有操作。 当在 ClusterRoleBinding 中使用时,可以授权对集群中以及所有名字空间中的全部资源进行完全控制。 当在 RoleBinding 中使用时,可以授权控制角色绑定所在名字空间中的所有资源,包括名字空间本身。 |
admin | 无 | 允许管理员访问权限,旨在使用 RoleBinding 在名字空间内执行授权。 如果在 RoleBinding 中使用,则可授予对名字空间中的大多数资源的读/写权限, 包括创建角色和角色绑定的能力。 此角色不允许对资源配额或者名字空间本身进行写操作。 此角色也不允许对 Kubernetes v1.22+ 创建的 EndpointSlices(或 Endpoints)进行写操作。 更多信息参阅 “EndpointSlices 和 Endpoints 写权限”小节。 |
edit | 无 | 允许对名字空间的大多数对象进行读/写操作。 此角色不允许查看或者修改角色或者角色绑定。 不过,此角色可以访问 Secret,以名字空间中任何 ServiceAccount 的身份运行 Pod, 所以可以用来了解名字空间内所有服务账户的 API 访问级别。 此角色也不允许对 Kubernetes v1.22+ 创建的 EndpointSlices(或 Endpoints)进行写操作。 更多信息参阅 “EndpointSlices 和 Endpoints 写操作”小节。 |
view | 无 | 允许对名字空间的大多数对象有只读权限。
它不允许查看角色或角色绑定。 此角色不允许查看 Secret,因为读取 Secret 的内容意味着可以访问名字空间中 ServiceAccount 的凭据信息,进而允许利用名字空间中任何 ServiceAccount 的身份访问 API(这是一种特权提升)。 |
默认 ClusterRole | 默认 ClusterRoleBinding | 描述 |
---|---|---|
system:kube-scheduler | system:kube-scheduler 用户 | 允许访问 scheduler 组件所需要的资源。 |
system:volume-scheduler | system:kube-scheduler 用户 | 允许访问 kube-scheduler 组件所需要的卷资源。 |
system:kube-controller-manager | system:kube-controller-manager 用户 | 允许访问控制器管理器组件所需要的资源。 各个控制回路所需要的权限在控制器角色详述。 |
system:node | 无 | 允许访问 kubelet 所需要的资源,包括对所有 Secret 的读操作和对所有 Pod 状态对象的写操作。 你应该使用 Node 鉴权组件和 NodeRestriction 准入插件而不是 system:node 角色。同时基于 kubelet 上调度执行的 Pod 来授权 kubelet 对 API 的访问。 system:node 角色的意义仅是为了与从 v1.8 之前版本升级而来的集群兼容。 |
system:node-proxier | system:kube-proxy 用户 | 允许访问 kube-proxy 组件所需要的资源。 |
默认 ClusterRole | 默认 ClusterRoleBinding | 描述 |
---|---|---|
system:auth-delegator | 无 | 允许将身份认证和鉴权检查操作外包出去。 这种角色通常用在插件式 API 服务器上,以实现统一的身份认证和鉴权。 |
system:heapster | 无 | 为 Heapster 组件(已弃用)定义的角色。 |
system:kube-aggregator | 无 | 为 kube-aggregator 组件定义的角色。 |
system:kube-dns | 在 kube-system 名字空间中的 kube-dns 服务账户 | 为 kube-dns 组件定义的角色。 |
system:kubelet-api-admin | 无 | 允许 kubelet API 的完全访问权限。 |
system:node-bootstrapper | 无 | 允许访问执行 kubelet TLS 启动引导 所需要的资源。 |
system:node-problem-detector | 无 | 为 node-problem-detector 组件定义的角色。 |
system:persistent-volume-provisioner | 无 | 允许访问大部分动态卷驱动所需要的资源。 |
system:monitoring | system:monitoring 组 | 允许对控制平面监控端点的读取访问(例如:kube-apiserver 存活和就绪端点(/healthz、/livez、/readyz), 各个健康检查端点(/healthz/*、/livez/*、/readyz/*)和 /metrics)。 请注意,各个运行状况检查端点和度量标准端点可能会公开敏感信息。 |
Kubernetes 控制器管理器运行内建于
Kubernetes 控制面的控制器。
当使用 --use-service-account-credentials
参数启动时,kube-controller-manager
使用单独的服务账户来启动每个控制器。
每个内置控制器都有相应的、前缀为 system:controller:
的角色。
如果控制管理器启动时未设置 --use-service-account-credentials
,
它使用自己的身份凭据来运行所有的控制器,该身份必须被授予所有相关的角色。
这些角色包括:
system:controller:attachdetach-controller
system:controller:certificate-controller
system:controller:clusterrole-aggregation-controller
system:controller:cronjob-controller
system:controller:daemon-set-controller
system:controller:deployment-controller
system:controller:disruption-controller
system:controller:endpoint-controller
system:controller:expand-controller
system:controller:generic-garbage-collector
system:controller:horizontal-pod-autoscaler
system:controller:job-controller
system:controller:namespace-controller
system:controller:node-controller
system:controller:persistent-volume-binder
system:controller:pod-garbage-collector
system:controller:pv-protection-controller
system:controller:pvc-protection-controller
system:controller:replicaset-controller
system:controller:replication-controller
system:controller:resourcequota-controller
system:controller:root-ca-cert-publisher
system:controller:route-controller
system:controller:service-account-controller
system:controller:service-controller
system:controller:statefulset-controller
system:controller:ttl-controller
RBAC API 会阻止用户通过编辑角色或者角色绑定来提升权限。 由于这一点是在 API 级别实现的,所以在 RBAC 鉴权组件未启用的状态下依然可以正常工作。
只有在符合下列条件之一的情况下,你才能创建/更新角色:
rbac.authorization.k8s.io
API 组中的 roles
或 clusterroles
资源使用 escalate
动词。例如,如果 user-1
没有列举集群范围所有 Secret 的权限,他将不能创建包含该权限的 ClusterRole。
若要允许用户创建/更新角色:
escalate
动作显式完成授权。
这里的 roles
和 clusterroles
资源包含在 rbac.authorization.k8s.io
API 组中。只有你已经具有了所引用的角色中包含的全部权限时,或者你被授权在所引用的角色上执行 bind
动词时,你才可以创建或更新角色绑定。这里的权限与角色绑定的作用域相同。
例如,如果用户 user-1
没有列举集群范围所有 Secret 的能力,则他不可以创建
ClusterRoleBinding 引用授予该许可权限的角色。
如要允许用户创建或更新角色绑定:
bind
动词的权限。例如,下面的 ClusterRole 和 RoleBinding 将允许用户 user-1
把名字空间 user-1-namespace
中的 admin
、edit
和 view
角色赋予其他用户:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: role-grantor
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings"]
verbs: ["create"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterroles"]
verbs: ["bind"]
# 忽略 resourceNames 意味着允许绑定任何 ClusterRole
resourceNames: ["admin","edit","view"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: role-grantor-binding
namespace: user-1-namespace
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: role-grantor
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: user-1
当启动引导第一个角色和角色绑定时,需要为初始用户授予他们尚未拥有的权限。 对初始角色和角色绑定进行初始化时需要:
system:masters
的凭据,该用户组由默认绑定关联到 cluster-admin
这个超级用户角色。kubectl create role
创建 Role 对象,定义在某一名字空间中的权限。例如:
创建名称为 “pod-reader” 的 Role 对象,允许用户对 Pods 执行 get
、watch
和 list
操作:
kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
创建名称为 “pod-reader” 的 Role 对象并指定 resourceNames
:
kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
创建名为 “foo” 的 Role 对象并指定 apiGroups
:
kubectl create role foo --verb=get,list,watch --resource=replicasets.apps
创建名为 “foo” 的 Role 对象并指定子资源权限:
kubectl create role foo --verb=get,list,watch --resource=pods,pods/status
创建名为 “my-component-lease-holder” 的 Role 对象,使其具有对特定名称的资源执行 get/update 的权限:
kubectl create role my-component-lease-holder --verb=get,list,watch,update --resource=lease --resource-name=my-component
kubectl create clusterrole
创建 ClusterRole 对象。例如:
创建名称为 “pod-reader” 的 ClusterRole 对象,允许用户对 Pods 对象执行 get
、
watch
和 list
操作:
kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods
创建名为 “pod-reader” 的 ClusterRole 对象并指定 resourceNames
:
kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
创建名为 “foo” 的 ClusterRole 对象并指定 apiGroups
:
kubectl create clusterrole foo --verb=get,list,watch --resource=replicasets.apps
创建名为 “foo” 的 ClusterRole 对象并指定子资源:
kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status
创建名为 “foo” 的 ClusterRole 对象并指定 nonResourceURL
:
kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/*
创建名为 “monitoring” 的 ClusterRole 对象并指定 aggregationRule
:
kubectl create clusterrole monitoring --aggregation-rule="rbac.example.com/aggregate-to-monitoring=true"
kubectl create rolebinding
在特定的名字空间中对 Role
或 ClusterRole
授权。例如:
在名字空间 “acme” 中,将名为 admin
的 ClusterRole 中的权限授予名称 “bob” 的用户:
kubectl create rolebinding bob-admin-binding --clusterrole=admin --user=bob --namespace=acme
在名字空间 “acme” 中,将名为 view
的 ClusterRole 中的权限授予名字空间 “acme”
中名为 myapp
的服务账户:
kubectl create rolebinding myapp-view-binding --clusterrole=view --serviceaccount=acme:myapp --namespace=acme
在名字空间 “acme” 中,将名为 view
的 ClusterRole 对象中的权限授予名字空间
“myappnamespace” 中名称为 myapp
的服务账户:
kubectl create rolebinding myappnamespace-myapp-view-binding --clusterrole=view --serviceaccount=myappnamespace:myapp --namespace=acme
kubectl create clusterrolebinding
在整个集群(所有名字空间)中用 ClusterRole 授权。例如:
在整个集群范围,将名为 cluster-admin
的 ClusterRole 中定义的权限授予名为 “root” 用户:
kubectl create clusterrolebinding root-cluster-admin-binding --clusterrole=cluster-admin --user=root
在整个集群范围内,将名为 system:node-proxier
的 ClusterRole 的权限授予名为
“system:kube-proxy” 的用户:
kubectl create clusterrolebinding kube-proxy-binding --clusterrole=system:node-proxier --user=system:kube-proxy
在整个集群范围内,将名为 view
的 ClusterRole 中定义的权限授予 “acme” 名字空间中名为
“myapp” 的服务账户:
kubectl create clusterrolebinding myapp-view-binding --clusterrole=view --serviceaccount=acme:myapp
kubectl auth reconcile
使用清单文件来创建或者更新 rbac.authorization.k8s.io/v1
API 对象。
尚不存在的对象会被创建,如果对应的名字空间也不存在,必要的话也会被创建。
已经存在的角色会被更新,使之包含输入对象中所给的权限。如果指定了
--remove-extra-permissions
,可以删除额外的权限。
已经存在的绑定也会被更新,使之包含输入对象中所给的主体。如果指定了
--remove-extra-permissions
,则可以删除多余的主体。
例如:
测试应用 RBAC 对象的清单文件,显示将要进行的更改:
kubectl auth reconcile -f my-rbac-rules.yaml --dry-run=client
应用 RBAC 对象的清单文件,保留角色(roles
)中的额外权限和绑定(bindings
)中的其他主体:
kubectl auth reconcile -f my-rbac-rules.yaml
应用 RBAC 对象的清单文件,删除角色(roles
)中的额外权限和绑定中的其他主体:
kubectl auth reconcile -f my-rbac-rules.yaml --remove-extra-subjects --remove-extra-permissions
默认的 RBAC 策略为控制面组件、节点和控制器授予权限。
但是不会对 kube-system
名字空间之外的服务账户授予权限。
(除了 API 发现角色 授予的权限)
这使得你可以根据需要向特定 ServiceAccount 授予特定权限。 细粒度的角色绑定可带来更好的安全性,但需要更多精力管理。 粗粒度的授权可能导致 ServiceAccount 被授予不必要的 API 访问权限(甚至导致潜在的权限提升), 但更易于管理。
按从最安全到最不安全的顺序,存在以下方法:
为特定应用的服务账户授予角色(最佳实践)
这要求应用在其 Pod 规约中指定 serviceAccountName
,
并额外创建服务账户(包括通过 API、应用程序清单、kubectl create serviceaccount
等)。
例如,在名字空间 “my-namespace” 中授予服务账户 “my-sa” 只读权限:
kubectl create rolebinding my-sa-view \
--clusterrole=view \
--serviceaccount=my-namespace:my-sa \
--namespace=my-namespace
将角色授予某名字空间中的 “default” 服务账户
如果某应用没有指定 serviceAccountName
,那么它将使用 “default” 服务账户。
"default" 服务账户所具有的权限会被授予给名字空间中所有未指定 serviceAccountName
的 Pod。
例如,在名字空间 "my-namespace" 中授予服务账户 "default" 只读权限:
kubectl create rolebinding default-view \
--clusterrole=view \
--serviceaccount=my-namespace:default \
--namespace=my-namespace
许多插件组件在 kube-system
名字空间以 “default” 服务账户运行。
要允许这些插件组件以超级用户权限运行,需要将集群的 cluster-admin
权限授予
kube-system
名字空间中的 “default” 服务账户。
启用这一配置意味着在 kube-system
名字空间中包含以超级用户账号来访问集群 API 的 Secret。
kubectl create clusterrolebinding add-on-cluster-admin \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:default
将角色授予名字空间中所有服务账户
如果你想要名字空间中所有应用都具有某角色,无论它们使用的什么服务账户, 可以将角色授予该名字空间的服务账户组。
例如,在名字空间 “my-namespace” 中的只读权限授予该名字空间中的所有服务账户:
kubectl create rolebinding serviceaccounts-view \
--clusterrole=view \
--group=system:serviceaccounts:my-namespace \
--namespace=my-namespace
在集群范围内为所有服务账户授予一个受限角色(不鼓励)
如果你不想管理每一个名字空间的权限,你可以向所有的服务账户授予集群范围的角色。
例如,为集群范围的所有服务账户授予跨所有名字空间的只读权限:
kubectl create clusterrolebinding serviceaccounts-view \
--clusterrole=view \
--group=system:serviceaccounts
授予超级用户访问权限给集群范围内的所有服务帐户(强烈不鼓励)
如果你不在乎如何区分权限,你可以将超级用户访问权限授予所有服务账户。
这样做会允许所有应用都对你的集群拥有完全的访问权限,并将允许所有能够读取 Secret(或创建 Pod)的用户对你的集群有完全的访问权限。
kubectl create clusterrolebinding serviceaccounts-cluster-admin \
--clusterrole=cluster-admin \
--group=system:serviceaccounts
在 Kubernetes v1.22 之前版本创建的集群里, “edit” 和 “admin” 聚合角色包含对 EndpointSlices(和 Endpoints)的写权限。 作为 CVE-2021-25740 的缓解措施, 此访问权限不包含在 Kubernetes 1.22 以及更高版本集群的聚合角色里。
升级到 Kubernetes v1.22 版本的现有集群不会包括此变化。 CVE 公告包含了在现有集群里限制此访问权限的指引。
如果你希望在新集群的聚合角色里保留此访问权限,你可以创建下面的 ClusterRole:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
kubernetes.io/description: |-
将端点写入权限添加到 edit 和 admin 角色。此特性因 CVE-2021-25740 在 1.22
中默认被移除。请参阅 https://issue.k8s.io/103675
这一设置将允许写者要求 LoadBalancer 或 Ingress 的实现向外暴露后端 IP 地址,
所暴露的 IP 地址无法通过其他方式访问,
并且可以规避对这些后端访问进行预防/隔离的网络策略或安全控制机制。
EndpointSlice 从未包含在 edit 和 admin 角色中,
因此 EndpointSlice API 没有什么可恢复的。
labels:
rbac.authorization.k8s.io/aggregate-to-edit: "true"
name: custom:aggregate-to-edit:endpoints # 你可以随意愿更改这个 name
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["create", "delete", "deletecollection", "patch", "update"]
原来运行较老版本 Kubernetes 的集群通常会使用限制宽松的 ABAC 策略, 包括授予所有服务帐户全权访问 API 的能力。
默认的 RBAC 策略为控制面组件、节点和控制器等授予有限的权限,但不会为
kube-system
名字空间外的服务账户授权(除了 API 发现角色授予的权限)。
这样做虽然安全得多,但可能会干扰期望自动获得 API 权限的现有工作负载。 这里有两种方法来完成这种转换:
同时运行 RBAC 和 ABAC 鉴权模式, 并指定包含现有的 ABAC 策略的策略文件:
--authorization-mode=...,RBAC,ABAC --authorization-policy-file=mypolicy.json
关于命令行中的第一个选项:如果早期的鉴权组件,例如 Node,拒绝了某个请求,则 RBAC 鉴权组件尝试对该 API 请求鉴权。如果 RBAC 也拒绝了该 API 请求,则运行 ABAC 鉴权组件。这意味着被 RBAC 或 ABAC 策略所允许的任何请求都是被允许的请求。
如果 kube-apiserver 启动时,RBAC 组件的日志级别为 5 或更高(--vmodule=rbac*=5
或 --v=5
),
你可以在 API 服务器的日志中看到 RBAC 拒绝的细节(前缀 RBAC
)
你可以使用这些信息来确定需要将哪些角色授予哪些用户、组或服务帐户。
一旦你将角色授予服务账户且工作负载运行时, 服务器日志中没有出现 RBAC 拒绝消息,就可以删除 ABAC 鉴权器。
你可以使用 RBAC 角色绑定复制宽松的 ABAC 策略。
下面的策略允许所有服务帐户充当集群管理员。 容器中运行的所有应用程序都会自动收到服务帐户的凭据,可以对 API 执行任何操作, 包括查看 Secret 和修改权限。这一策略是不被推荐的。
kubectl create clusterrolebinding permissive-binding \
--clusterrole=cluster-admin \
--user=admin \
--user=kubelet \
--group=system:serviceaccounts
在你完成到 RBAC 的迁移后,应该调整集群的访问控制,确保相关的策略满足你的信息安全需求。
节点鉴权是一种特殊用途的鉴权模式,专门对 kubelet 发出的 API 请求进行授权。
节点鉴权器允许 kubelet 执行 API 操作。包括:
读取操作:
Kubernetes v1.31 [alpha]
(enabled by default: false)当启用 AuthorizeNodeWithSelectors
特性
(以及作为先决条件的 AuthorizeWithSelectors
特性)时,
kubelet 只允许读取它们自己的 Node 对象,
并且只允许读取绑定到其节点的 Pod。
写入操作:
NodeRestriction
准入插件以限制 kubelet 只能修改自己的节点)NodeRestriction
准入插件以限制 kubelet 只能修改绑定到自身的 Pod)身份认证与鉴权相关的操作:
在将来的版本中,节点鉴权器可能会添加或删除权限,以确保 kubelet 具有正确操作所需的最小权限集。
为了获得节点鉴权器的授权,kubelet 必须使用一个凭据以表示它在 system:nodes
组中,用户名为 system:node:<nodeName>
。上述的组名和用户名格式要与
kubelet TLS 启动引导
过程中为每个 kubelet 创建的标识相匹配。
<nodeName>
的值必须与 kubelet 注册的节点名称精确匹配。默认情况下,节点名称是由
hostname
提供的主机名,或者通过 kubelet --hostname-override
选项覆盖。
但是,当使用 --cloud-provider
kubelet 选项时,具体的主机名可能由云提供商确定,
忽略本地的 hostname
和 --hostname-override
选项。有关
kubelet 如何确定主机名的详细信息,请参阅
kubelet 选项参考。
要启用节点鉴权器,请使用 --authorization-mode=Node
启动 API 服务器。
要限制 kubelet 可以写入的 API 对象,请使用
--enable-admission-plugins=...,NodeRestriction,...
启动 API 服务器,从而启用
NodeRestriction
准入插件。
system:nodes
组之外的 kubeletsystem:nodes
组之外的 kubelet 不会被 Node
鉴权模式授权,并且需要继续通过当前授权它们的机制来授权。
节点准入插件不会限制来自这些 kubelet 的请求。
在一些部署中,kubelet 具有 system:nodes
组的凭据,
但是无法给出它们所关联的节点的标识,因为它们没有 system:node:...
格式的用户名。
这些 kubelet 不会被 Node
鉴权模式授权,并且需要继续通过当前授权它们的任何机制来授权。
因为默认的节点标识符实现不会把它当作节点身份标识,NodeRestriction
准入插件会忽略来自这些 kubelet 的请求。
WebHook 是一种 HTTP 回调:某些条件下触发的 HTTP POST 请求;通过 HTTP POST 发送的简单事件通知。一个基于 web 应用实现的 WebHook 会在特定事件发生时把消息发送给特定的 URL。
具体来说,当在判断用户权限时,Webhook
模式会使 Kubernetes 查询外部的 REST 服务。
Webhook
模式需要一个 HTTP 配置文件,通过
--authorization-webhook-config-file=SOME_FILENAME
的参数声明。
配置文件的格式使用 kubeconfig。 在该文件中,“users” 代表着 API 服务器的 Webhook,而 “cluster” 代表着远程服务。
使用 HTTPS 客户端认证的配置例子:
# Kubernetes API 版本
apiVersion: v1
# API 对象种类
kind: Config
# clusters 代表远程服务
clusters:
- name: name-of-remote-authz-service
cluster:
# 对远程服务进行身份认证的 CA
certificate-authority: /path/to/ca.pem
# 远程服务的查询 URL。必须使用 'https'。不可以包含参数。
server: https://authz.example.com/authorize
# users 代表 API 服务器的 webhook 配置
users:
- name: name-of-api-server
user:
client-certificate: /path/to/cert.pem # 要使用的 webhook 插件的证书
client-key: /path/to/key.pem # 与证书匹配的密钥
# kubeconfig 文件必须有 context。需要提供一个给 API 服务器。
current-context: webhook
contexts:
- context:
cluster: name-of-remote-authz-service
user: name-of-api-server
name: webhook
在做认证决策时,API 服务器会 POST 一个 JSON 序列化的 authorization.k8s.io/v1beta1
SubjectAccessReview
对象来描述这个动作。这个对象包含了描述用户请求的字段,同时也包含了需要被访问资源或请求特征的具体信息。
需要注意的是 webhook API 对象与其他 Kubernetes API
对象一样都同样都遵从版本兼容规则。
实施人员应该了解 beta 对象的更宽松的兼容性承诺,同时确认请求的 "apiVersion" 字段能被正确地反序列化。
此外,API 服务器还必须启用 authorization.k8s.io/v1beta1
API
扩展组 (--runtime-config=authorization.k8s.io/v1beta1=true
)。
一个请求内容的例子:
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"spec": {
"resourceAttributes": {
"namespace": "kittensandponies",
"verb": "get",
"group": "unicorn.example.org",
"resource": "pods"
},
"user": "jane",
"group": [
"group1",
"group2"
]
}
}
期待远程服务填充请求的 status
字段并响应允许或禁止访问。
响应主体的 spec
字段被忽略,可以省略。允许的响应将返回:
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"status": {
"allowed": true
}
}
为了禁止访问,有两种方法。
在大多数情况下,第一种方法是首选方法,它指示授权 Webhook 不允许或对请求 “无意见”。 但是,如果配置了其他授权者,则可以给他们机会允许请求。 如果没有其他授权者,或者没有一个授权者,则该请求被禁止。Webhook 将返回:
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"status": {
"allowed": false,
"reason": "user does not have read access to the namespace"
}
}
第二种方法立即拒绝其他配置的授权者进行短路评估。 仅应由对集群的完整授权者配置有详细了解的 Webhook 使用。Webhook 将返回:
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"status": {
"allowed": false,
"denied": true,
"reason": "user does not have read access to the namespace"
}
}
对于非资源的路径访问是这么发送的:
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"spec": {
"nonResourceAttributes": {
"path": "/debug",
"verb": "get"
},
"user": "jane",
"group": [
"group1",
"group2"
]
}
}
Kubernetes v1.31 [alpha]
(enabled by default: false)启用 AuthorizeWithSelectors
特性后,请求中的字段和标签选择算符将被传递给授权 Webhook。
此 Webhook 可以根据作用域字段和标签选择算符做出授权决策(如果它愿意的话)。
SubjectAccessReview API 文档提供了这些字段应如何被授权 Webhook 解释和处理的指南,特别是应使用解析后的要求而不是原始选择算符字符串,以及如何安全地处理未识别的操作符。
{
"apiVersion": "authorization.k8s.io/v1beta1",
"kind": "SubjectAccessReview",
"spec": {
"resourceAttributes": {
"verb": "list",
"group": "",
"resource": "pods",
"fieldSelector": {
"requirements": [
{"key":"spec.nodeName", "operator":"In", "values":["mynode"]}
]
},
"labelSelector": {
"requirements": [
{"key":"example.com/mykey", "operator":"In", "values":["myvalue"]}
]
}
},
"user": "jane",
"group": [
"group1",
"group2"
]
}
}
非资源类的路径包括:/api
、/apis
、/metrics
、/logs
、/debug
、
/healthz
、/livez
、/openapi/v2
、/readyz
、和 /version
。
客户端需要访问 /api
、/api/*
、/apis
、/apis/*
和 /version
以便
能发现服务器上有什么资源和版本。对于其他非资源类的路径访问在没有 REST API 访问限制的情况下拒绝。
更多信息请参阅 SubjectAccessReview API 文档和 webhook.go 实现。
基于属性的访问控制(Attribute-based access control,ABAC)定义了访问控制范例, ABAC 通过使用将属性组合在一起的策略来向用户授予访问权限。
要启用 ABAC
模式,可以在启动时指定 --authorization-policy-file=SOME_FILENAME
和 --authorization-mode=ABAC
。
此文件格式是每行一个 JSON 对象,不应存在外层的列表或映射,每行应只有一个映射。
每一行都是一个“策略对象”,策略对象是具有以下属性的映射:
apiVersion
,字符串类型:有效值为 abac.authorization.kubernetes.io/v1beta1
,允许对策略格式进行版本控制和转换。kind
,字符串类型:有效值为 Policy
,允许对策略格式进行版本控制和转换。spec
配置为具有以下映射的属性:user
,字符串类型;来自 --token-auth-file
的用户字符串,如果你指定 user
,它必须与验证用户的用户名匹配。group
,字符串类型;如果指定 group
,它必须与经过身份验证的用户的一个组匹配,
system:authenticated
匹配所有经过身份验证的请求。
system:unauthenticated
匹配所有未经过身份验证的请求。apiGroup
,字符串类型;一个 API 组。apps
、networking.k8s.io
*
匹配所有 API 组。namespace
,字符串类型;一个命名空间。kube-system
*
匹配所有资源请求。resource
,字符串类型;资源类型。pods
、deployments
*
匹配所有资源请求。nonResourcePath
,字符串类型;非资源请求路径。/version
或 /apis
*
匹配所有非资源请求。/foo/*
匹配 /foo/
的所有子路径。readonly
,布尔值类型。如果为 true,则表示该策略仅适用于 get、list 和 watch 操作。
非资源匹配属性仅适用于 get 操作。属性未设置等效于属性被设置为对应类型的零值(例如空字符串、0、false)。 然而,出于可读性考虑,应尽量选择不设置这类属性。
在将来,策略可能以 JSON 格式表示,并通过 REST 界面进行管理。
请求具有与策略对象的属性对应的属性。
当接收到请求时,属性是确定的。未知属性设置为其类型的零值(例如:空字符串、0、false)。
设置为 "*"
的属性将匹配相应属性的任何值。
检查属性的元组,以匹配策略文件中的每个策略。如果至少有一行匹配请求属性, 则请求被鉴权(但仍可能无法通过稍后的合法性检查)。
要允许任何经过身份验证的用户执行某些操作,请将策略组属性设置为 "system:authenticated"
。
要允许任何未经身份验证的用户执行某些操作,请将策略组属性设置为 "system:unauthenticated"
。
要允许用户执行任何操作,请使用设置为 "*"
的 apiGroup、namespace、resource 和
nonResourcePath 属性编写策略。
kubectl 使用 apiserver 的 /api
和 /apis
端点来发现服务资源类型,
并使用位于 /openapi/v2
的模式信息来验证通过创建/更新操作发送到 API 的对象。
当使用 ABAC 鉴权时,这些特殊资源必须显式地通过策略中的 nonResourcePath
属性暴露出来
(参见下面的 示例):
/api
,/api/*
,/apis
和 /apis/*
用于 API 版本协商。/version
通过 kubectl version
检索服务器版本。/swaggerapi/*
用于创建 / 更新操作。要检查涉及到特定 kubectl 操作的 HTTP 调用,你可以调整详细程度:
kubectl --v=8 version
Alice 可以对所有资源做任何事情:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "alice", "namespace": "*", "resource": "*", "apiGroup": "*"}}
kubelet 可以读取所有 Pod:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "kubelet", "namespace": "*", "resource": "pods", "readonly": true}}
kubelet 可以读写事件:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "kubelet", "namespace": "*", "resource": "events"}}
Bob 可以在命名空间 projectCaribou
中读取 Pod:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "bob", "namespace": "projectCaribou", "resource": "pods", "readonly": true}}
任何人都可以对所有非资源路径进行只读请求:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group": "system:authenticated", "readonly": true, "nonResourcePath": "*"}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group": "system:unauthenticated", "readonly": true, "nonResourcePath": "*"}}
每个服务账号都有对应的 ABAC 用户名,服务账号的用户名是根据命名约定生成的:
system:serviceaccount:<namespace>:<serviceaccountname>
创建新的命名空间也会导致创建一个新的服务账号:
system:serviceaccount:<namespace>:default
例如,如果你要使用 ABAC 将(kube-system
命名空间中)的默认服务账号完整权限授予 API,
则可以将此行添加到策略文件中:
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"system:serviceaccount:kube-system:default","namespace":"*","resource":"*","apiGroup":"*"}}
API 服务器将需要被重新启动以获取新的策略行。
此页面提供准入控制器(Admission Controller)的概述。
准入控制器 是一段代码,它会在请求通过认证和鉴权之后、对象被持久化之前拦截到达 API 服务器的请求。
准入控制器可以执行验证(Validating) 和/或变更(Mutating) 操作。 变更(mutating)控制器可以根据被其接受的请求更改相关对象;验证(validating)控制器则不行。
准入控制器限制创建、删除、修改对象的请求。 准入控制器也可以阻止自定义动作,例如通过 API 服务器代理连接到 Pod 的请求。 准入控制器不会 (也不能)阻止读取(get、watch 或 list)对象的请求。
Kubernetes 1.31
中的准入控制器由下面的列表组成,
并编译进 kube-apiserver
可执行文件,并且只能由集群管理员配置。
在该列表中,有两个特殊的控制器:MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook。
它们根据 API 中的配置,
分别执行变更和验证准入控制 Webhook。
准入控制过程分为两个阶段。第一阶段,运行变更准入控制器。第二阶段,运行验证准入控制器。 再次提醒,某些控制器既是变更准入控制器又是验证准入控制器。
如果两个阶段之一的任何一个控制器拒绝了某请求,则整个请求将立即被拒绝,并向最终用户返回错误。
最后,除了对对象进行变更外,准入控制器还可能有其它副作用:将相关资源作为请求处理的一部分进行变更。 增加配额用量就是一个典型的示例,说明了这样做的必要性。 此类用法都需要相应的回收或回调过程,因为任一准入控制器都无法确定某个请求能否通过所有其它准入控制器。
Kubernetes 的若干重要功能都要求启用一个准入控制器,以便正确地支持该特性。 因此,没有正确配置准入控制器的 Kubernetes API 服务器是不完整的,它无法支持你所期望的所有特性。
Kubernetes API 服务器的 enable-admission-plugins
标志接受一个(以逗号分隔的)准入控制插件列表,
这些插件会在集群修改对象之前被调用。
例如,下面的命令启用 NamespaceLifecycle
和 LimitRanger
准入控制插件:
kube-apiserver --enable-admission-plugins=NamespaceLifecycle,LimitRanger ...
根据你 Kubernetes 集群的部署方式以及 API 服务器的启动方式,你可能需要以不同的方式应用设置。 例如,如果将 API 服务器部署为 systemd 服务,你可能需要修改 systemd 单元文件; 如果以自托管方式部署 Kubernetes,你可能需要修改 API 服务器的清单文件。
Kubernetes API 服务器的 disable-admission-plugins
标志,会将传入的(以逗号分隔的)
准入控制插件列表禁用,即使是默认启用的插件也会被禁用。
kube-apiserver --disable-admission-plugins=PodNodeSelector,AlwaysDeny ...
要查看哪些插件是被启用的:
kube-apiserver -h | grep enable-admission-plugins
在 Kubernetes 1.31 中,默认启用的插件有:
CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, DefaultStorageClass, DefaultTolerationSeconds, LimitRanger, MutatingAdmissionWebhook, NamespaceLifecycle, PersistentVolumeClaimResize, PodSecurity, Priority, ResourceQuota, RuntimeClass, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionPolicy, ValidatingAdmissionWebhook
ValidatingAdmissionPolicy
准入插件默认被启用,
但只有启用 ValidatingAdmissionPolicy
特性门控 和
admissionregistration.k8s.io/v1alpha1
API 时才会激活。
Kubernetes v1.13 [deprecated]
类别:验证。
该准入控制器允许所有的 Pod 进入集群。此插件已被弃用,因其行为与没有准入控制器一样。
Kubernetes v1.13 [deprecated]
类别:验证。
拒绝所有的请求。由于它没有实际意义,已被弃用。
该准入控制器会修改每个新创建的 Pod,将其镜像拉取策略设置为 Always
。
这在多租户集群中是有用的,这样用户就可以放心,他们的私有镜像只能被那些有凭证的人使用。
如果没有这个准入控制器,一旦镜像被拉取到节点上,任何用户的 Pod 都可以通过已了解到的镜像的名称
(假设 Pod 被调度到正确的节点上)来使用它,而不需要对镜像进行任何鉴权检查。
启用这个准入控制器之后,启动容器之前必须拉取镜像,这意味着需要有效的凭证。
类别:验证。
此准入控制器获取审批 CertificateSigningRequest 资源的请求并执行额外的鉴权检查,
以确保针对设置了 spec.signerName
的 CertificateSigningRequest 资源而言,
审批请求的用户有权限对证书请求执行 审批 操作。
有关对 CertificateSigningRequest 资源执行不同操作所需权限的详细信息, 请参阅证书签名请求。
类别:验证。
此准入控制器监视对 CertificateSigningRequest 资源的 status.certificate
字段的更新请求,
并执行额外的鉴权检查,以确保针对设置了 spec.signerName
的 CertificateSigningRequest 资源而言,
签发证书的用户有权限对证书请求执行 签发 操作。
有关对 CertificateSigningRequest 资源执行不同操作所需权限的详细信息, 请参阅证书签名请求。
类别:验证。
此准入控制器监视 spec.signerName
被设置为 kubernetes.io/kube-apiserver-client
的
CertificateSigningRequest 资源创建请求,并拒绝所有将 “group”(或 “organization attribute”)
设置为 system:masters
的请求。
类别:变更。
该准入控制器监测没有请求任何特定 Ingress 类的 Ingress
对象创建请求,并自动向其添加默认 Ingress 类。
这样,没有任何特殊 Ingress 类需求的用户根本不需要关心它们,他们将被设置为默认 Ingress 类。
当未配置默认 Ingress 类时,此准入控制器不执行任何操作。如果有多个 Ingress 类被标记为默认 Ingress 类,
此控制器将拒绝所有创建 Ingress
的操作,并返回错误信息。
要修复此错误,管理员必须重新检查其 IngressClass
对象,并仅将其中一个标记为默认
(通过注解 "ingressclass.kubernetes.io/is-default-class")。
此准入控制器会忽略所有 Ingress
更新操作,仅处理创建操作。
关于 Ingress 类以及如何将 Ingress 类标记为默认的更多信息,请参见 Ingress 页面。
类别:变更。
此准入控制器监测没有请求任何特定存储类的 PersistentVolumeClaim
对象的创建请求,
并自动向其添加默认存储类。
这样,没有任何特殊存储类需求的用户根本不需要关心它们,它们将被设置为使用默认存储类。
当未配置默认存储类时,此准入控制器不执行任何操作。如果将多个存储类标记为默认存储类,
此控制器将拒绝所有创建 PersistentVolumeClaim
的请求,并返回错误信息。
要修复此错误,管理员必须重新检查其 StorageClass
对象,并仅将其中一个标记为默认。
此准入控制器会忽略所有 PersistentVolumeClaim
更新操作,仅处理创建操作。
关于持久卷申领和存储类,以及如何将存储类标记为默认, 请参见持久卷页面。
类别:变更。
此准入控制器基于 k8s-apiserver 的输入参数 default-not-ready-toleration-seconds
和
default-unreachable-toleration-seconds
为 Pod 设置默认的容忍度,以容忍 notready:NoExecute
和
unreachable:NoExecute
污点
(如果 Pod 尚未容忍 node.kubernetes.io/not-ready:NoExecute
和
node.kubernetes.io/unreachable:NoExecute
污点的话)。
default-not-ready-toleration-seconds
和 default-unreachable-toleration-seconds
的默认值是 5 分钟。
类别:验证。
此准入控制器拒绝新的 Service
中使用字段 externalIPs
。
此功能非常强大(允许网络流量拦截),并且无法很好地受策略控制。
启用后,集群用户将无法创建使用 externalIPs
的新 Service
,也无法在现有
Service
对象上为 externalIPs
添加新值。
externalIPs
的现有使用不受影响,用户可以在现有 Service
对象上从
externalIPs
中删除值。
大多数用户根本不需要此特性,集群管理员应考虑将其禁用。
确实需要使用此特性的集群应考虑使用一些自定义策略来管理 externalIPs
的使用。
此准入控制器默认被禁用。
Kubernetes v1.13 [alpha]
类别:验证。
此准入控制器缓解了请求存储新事件时淹没 API 服务器的问题。集群管理员可以通过以下方式指定事件速率限制:
EventRateLimit
准入控制器;--admission-control-config-file
设置的文件中,
引用 EventRateLimit
配置文件:apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: EventRateLimit
path: eventconfig.yaml
...
可以在配置中指定的限制有四种类型:
Server
:API 服务器收到的所有(创建或修改)Event 请求共享一个桶。Namespace
:每个名字空间都对应一个专用的桶。User
:为每个用户分配一个桶。SourceAndObject
:根据事件的来源和涉及对象的各种组合分配桶。下面是一个针对此配置的 eventconfig.yaml
示例:
apiVersion: eventratelimit.admission.k8s.io/v1alpha1
kind: Configuration
limits:
- type: Namespace
qps: 50
burst: 100
cacheSize: 2000
- type: User
qps: 10
burst: 50
详情请参见 EventRateLimit 配置 API 文档(v1alpha1)。
此准入控制器默认被禁用。
类别:变更。
此插件有助于创建带有扩展资源的专用节点。 如果运维人员想要创建带有扩展资源(如 GPU、FPGA 等)的专用节点,他们应该以扩展资源名称作为键名, 为节点设置污点。 如果启用了此准入控制器,会将此类污点的容忍度自动添加到请求扩展资源的 Pod 中, 用户不必再手动添加这些容忍度。
此准入控制器默认被禁用。
类别:验证。
ImagePolicyWebhook 准入控制器允许使用后端 Webhook 做出准入决策。
此准入控制器默认被禁用。
ImagePolicyWebhook 使用配置文件来为后端行为设置选项。该文件可以是 JSON 或 YAML, 并具有以下格式:
imagePolicy:
kubeConfigFile: /path/to/kubeconfig/for/backend
# 以秒计的时长,控制批准请求的缓存时间
allowTTL: 50
# 以秒计的时长,控制拒绝请求的缓存时间
denyTTL: 50
# 以毫秒计的时长,控制重试间隔
retryBackoff: 500
# 确定 Webhook 后端失效时的行为
defaultAllow: true
在通过命令行标志 --admission-control-config-file
为 API 服务器提供的文件中,
引用 ImagePolicyWebhook 配置文件:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook
path: imagepolicyconfig.yaml
...
或者,你也可以直接将配置嵌入到该文件中:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook
configuration:
imagePolicy:
kubeConfigFile: <kubeconfig 文件路径>
allowTTL: 50
denyTTL: 50
retryBackoff: 500
defaultAllow: true
ImagePolicyWebhook 的配置文件必须引用 kubeconfig 格式的文件;该文件用来设置与后端的连接。要求后端使用 TLS 进行通信。
kubeconfig 文件的 clusters
字段需要指向远端服务,users
字段需要包含已返回的授权者。
# clusters 指的是远程服务。
clusters:
- name: name-of-remote-imagepolicy-service
cluster:
certificate-authority: /path/to/ca.pem # CA 用于验证远程服务
server: https://images.example.com/policy # 要查询的远程服务的 URL,必须是 'https'。
# users 指的是 API 服务器的 Webhook 配置。
users:
- name: name-of-api-server
user:
client-certificate: /path/to/cert.pem # Webhook 准入控制器使用的证书
client-key: /path/to/key.pem # 证书匹配的密钥
关于 HTTP 配置的更多信息,请参阅 kubeconfig 文档。
当面对一个准入决策时,API 服务器发送一个描述操作的 JSON 序列化的
imagepolicy.k8s.io/v1alpha1
ImageReview
对象。
该对象包含描述被准入容器的字段,以及与 *.image-policy.k8s.io/*
匹配的所有 Pod 注解。
注意,Webhook API 对象与其他 Kubernetes API 对象一样受制于相同的版本控制兼容性规则。
实现者应该知道对 alpha 对象兼容性是相对宽松的,并检查请求的 "apiVersion" 字段,
以确保正确的反序列化。此外,API 服务器必须启用 imagepolicy.k8s.io/v1alpha1
API 扩展组
(--runtime-config=imagepolicy.k8s.io/v1alpha1=true
)。
请求体示例:
{
"apiVersion": "imagepolicy.k8s.io/v1alpha1",
"kind": "ImageReview",
"spec": {
"containers": [
{
"image": "myrepo/myimage:v1"
},
{
"image": "myrepo/myimage@sha256:beb6bd6a68f114c1dc2ea4b28db81bdf91de202a9014972bec5e4d9171d90ed"
}
],
"annotations": {
"mycluster.image-policy.k8s.io/ticket-1234": "break-glass"
},
"namespace": "mynamespace"
}
}
远程服务将填充请求的 status
字段,并返回允许或不允许访问的响应。
响应体的 spec
字段会被忽略,并且可以被省略。一个允许访问应答会返回:
{
"apiVersion": "imagepolicy.k8s.io/v1alpha1",
"kind": "ImageReview",
"status": {
"allowed": true
}
}
若不允许访问,服务将返回:
{
"apiVersion": "imagepolicy.k8s.io/v1alpha1",
"kind": "ImageReview",
"status": {
"allowed": false,
"reason": "image currently blacklisted"
}
}
更多的文档,请参阅 imagepolicy.v1alpha1
API。
一个 Pod 中匹配 *.image-policy.k8s.io/*
的注解都会被发送给 Webhook。
这样做使得了解后端镜像策略的用户可以向它发送额外的信息,
并让不同的后端实现接收不同的信息。
你可以在这里输入的信息有:
在任何情况下,注解都是由用户提供的,并不会被 Kubernetes 以任何方式进行验证。
类别:验证。
此准入控制器拒绝定义了 AntiAffinity
拓扑键的任何 Pod
(requiredDuringSchedulingRequiredDuringExecution
中的 kubernetes.io/hostname
除外)。
此准入控制器默认被禁用。
类别:变更和验证。
此准入控制器会监测传入的请求,并确保请求不会违反 Namespace
中 LimitRange
对象所设置的任何约束。
如果你在 Kubernetes 部署中使用了 LimitRange
对象,则必须使用此准入控制器来执行这些约束。
LimitRanger 还可以用于将默认资源请求应用到没有设定资源约束的 Pod;
当前,默认的 LimitRanger 对 default
名字空间中的所有 Pod 都设置 0.1 CPU 的需求。
请查看 limitRange API 文档和 LimitRange 例子以了解更多细节。
类别:变更。
此准入控制器调用任何与请求匹配的变更(Mutating) Webhook。匹配的 Webhook 将被顺序调用。 每一个 Webhook 都可以自由修改对象。
MutatingAdmissionWebhook
,顾名思义,仅在变更阶段运行。
如果由此准入控制器调用的 Webhook 有副作用(如:减少配额), 则它 必须 具有协调系统,因为不能保证后续的 Webhook 和验证准入控制器都会允许完成请求。
如果你禁用了 MutatingAdmissionWebhook,那么还必须使用 --runtime-config
标志禁止
admissionregistration.k8s.io/v1
组/版本中的 MutatingWebhookConfiguration
,
二者都是默认启用的。
类别:变更。
此准入控制器会检查针对名字空间域资源的所有传入请求,并检查所引用的名字空间是否确实存在。 如果找不到所引用的名字空间,控制器将创建一个名字空间。 此准入控制器对于不想要求名字空间必须先创建后使用的集群部署很有用。
类别:验证。
此准入控制器检查针对名字空间作用域的资源(除 Namespace
自身)的所有请求。
如果请求引用的名字空间不存在,则拒绝该请求。
类别:验证。
该准入控制器禁止在一个正在被终止的 Namespace
中创建新对象,并确保针对不存在的
Namespace
的请求被拒绝。该准入控制器还会禁止删除三个系统保留的名字空间,即 default
、
kube-system
和 kube-public
。
Namespace
的删除操作会触发一系列删除该名字空间中所有对象(Pod、Service 等)的操作。
为了确保这个过程的完整性,我们强烈建议启用这个准入控制器。
类别:验证。
该准入控制器限制了某 kubelet 可以修改的 Node
和 Pod
对象。
为了受到这个准入控制器的限制,kubelet 必须使用在 system:nodes
组中的凭证,
并使用 system:node:<nodeName>
形式的用户名。
这样,kubelet 只可修改自己的 Node
API 对象,只能修改绑定到自身节点的 Pod 对象。
不允许 kubelet 更新或删除 Node
API 对象的污点。
NodeRestriction
准入插件可防止 kubelet 删除其 Node
API 对象,
并对前缀为 kubernetes.io/
或 k8s.io/
的标签的修改对 kubelet 作如下限制:
node-restriction.kubernetes.io/
的标签。
这类前缀的标签时保留给管理员的,用以为 Node
对象设置标签以隔离工作负载,而不允许 kubelet
修改带有该前缀的标签。kubernetes.io/hostname
kubernetes.io/arch
kubernetes.io/os
beta.kubernetes.io/instance-type
node.kubernetes.io/instance-type
failure-domain.beta.kubernetes.io/region
(已弃用)failure-domain.beta.kubernetes.io/zone
(已弃用)topology.kubernetes.io/region
topology.kubernetes.io/zone
kubelet.kubernetes.io/
为前缀的标签node.kubernetes.io/
为前缀的标签以 kubernetes.io
或 k8s.io
为前缀的所有其他标签都限制 kubelet 使用,并且将来可能会被
NodeRestriction
准入插件允许或禁止。
将来的版本可能会增加其他限制,以确保 kubelet 具有正常运行所需的最小权限集。
类别:验证。
此准入控制器保护对对象的 metadata.ownerReferences
的访问,以便只有对该对象具有
delete 权限的用户才能对其进行更改。
该准入控制器还保护对 metadata.ownerReferences[x].blockOwnerDeletion
对象的访问,
以便只有对所引用的 属主(owner) 的 finalizers
子资源具有 update
权限的用户才能对其进行更改。
Kubernetes v1.24 [stable]
类别:验证。
此准入控制器检查传入的 PersistentVolumeClaim
调整大小请求,对其执行额外的验证检查操作。
建议启用 PersistentVolumeClaimResize
准入控制器。除非 PVC 的 StorageClass
明确地将
allowVolumeExpansion
设置为 true
来显式启用调整大小。
否则,默认情况下该准入控制器会阻止所有对 PVC 大小的调整。
例如:由以下 StorageClass
创建的所有 PersistentVolumeClaim
都支持卷容量扩充:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gluster-vol-default
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://192.168.10.100:8080"
restuser: ""
secretNamespace: ""
secretName: ""
allowVolumeExpansion: true
关于持久化卷申领的更多信息,请参见 PersistentVolumeClaim。
Kubernetes v1.5 [alpha]
类别:验证。
此准入控制器通过读取名字空间注解和全局配置,来为名字空间中可以使用的节点选择器设置默认值并实施限制。
此准入控制器默认被禁用。
PodNodeSelector
使用配置文件来设置后端行为的选项。
请注意,配置文件格式将在将来某个版本中改为版本化文件。
该文件可以是 JSON 或 YAML,格式如下:
podNodeSelectorPluginConfig:
clusterDefaultNodeSelector: name-of-node-selector
namespace1: name-of-node-selector
namespace2: name-of-node-selector
通过 API 服务器命令行标志 --admission-control-config-file
为 API 服务器提供的文件中,
需要引用 PodNodeSelector
配置文件:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodNodeSelector
path: podnodeselector.yaml
...
PodNodeSelector
使用键为 scheduler.alpha.kubernetes.io/node-selector
的注解为名字空间设置节点选择算符。
apiVersion: v1
kind: Namespace
metadata:
annotations:
scheduler.alpha.kubernetes.io/node-selector: name-of-node-selector
name: namespace3
此准入控制器行为如下:
Namespace
的注解带有键 scheduler.alpha.kubernetes.io/node-selector
,
则将其值用作节点选择算符。PodNodeSelector
插件配置文件中定义的
clusterDefaultNodeSelector
作为节点选择算符。PodNodeSelector 允许 Pod 强制在特定标签的节点上运行。 另请参阅 PodTolerationRestriction 准入插件,该插件可防止 Pod 在特定污点的节点上运行。
Kubernetes v1.25 [stable]
类别:验证。
PodSecurity 准入控制器在新 Pod 被准入之前对其进行检查, 根据请求的安全上下文和 Pod 所在名字空间允许的 Pod 安全性标准的限制来确定新 Pod 是否应该被准入。
更多信息请参阅 Pod 安全性准入。
PodSecurity 取代了一个名为 PodSecurityPolicy 的旧准入控制器。
Kubernetes v1.7 [alpha]
类别:变更和验证。
准入控制器 PodTolerationRestriction 检查 Pod 的容忍度与其名字空间的容忍度之间是否存在冲突。 如果存在冲突,则拒绝 Pod 请求。 控制器接下来会将名字空间的容忍度合并到 Pod 的容忍度中, 根据名字空间的容忍度白名单检查所得到的容忍度结果。 如果检查成功,则将接受 Pod 请求,否则拒绝该请求。
如果 Pod 的名字空间没有任何关联的默认容忍度或容忍度白名单, 则使用集群级别的默认容忍度或容忍度白名单(如果有的话)。
名字空间的容忍度通过注解键 scheduler.alpha.kubernetes.io/defaultTolerations
来设置。可接受的容忍度可以通过 scheduler.alpha.kubernetes.io/tolerationsWhitelist
注解键来添加。
名字空间注解的示例:
apiVersion: v1
kind: Namespace
metadata:
name: apps-that-need-nodes-exclusively
annotations:
scheduler.alpha.kubernetes.io/defaultTolerations: '[{"operator": "Exists", "effect": "NoSchedule", "key": "dedicated-node"}]'
scheduler.alpha.kubernetes.io/tolerationsWhitelist: '[{"operator": "Exists", "effect": "NoSchedule", "key": "dedicated-node"}]'
此准入控制器默认被禁用。
类别:变更和验证。
优先级准入控制器使用 priorityClassName
字段并用整型值填充优先级。
如果找不到优先级,则拒绝 Pod。
类别:验证。
此准入控制器会监测传入的请求,并确保它不违反任何一个 Namespace
中的 ResourceQuota
对象中列举的约束。如果你在 Kubernetes 部署中使用了 ResourceQuota
,
则必须使用这个准入控制器来强制执行配额限制。
请参阅 resourceQuota API 参考 和 Resource Quota 例子了解更多细节。
类别:变更和验证。
如果你所定义的 RuntimeClass 包含 Pod 开销,
这个准入控制器会检查新的 Pod。
被启用后,此准入控制器会拒绝所有已经设置了 overhead 字段的 Pod 创建请求。
对于配置了 RuntimeClass 并在其 .spec
中选定 RuntimeClass 的 Pod,
此准入控制器会根据相应 RuntimeClass 中定义的值为 Pod 设置 .spec.overhead
。
详情请参见 Pod 开销。
类别:变更和验证。
此准入控制器实现了
ServiceAccount
的自动化。强烈推荐为 Kubernetes 项目启用此准入控制器。
如果你打算使用 Kubernetes 的 ServiceAccount
对象,你应启用这个准入控制器。
关于 kubernetes.io/enforce-mountable-secrets
注解:尽管注解的名称表明它只涉及 Secret 的挂载,
但其执行范围也扩展到 Pod 上下文中 Secret 的其他使用方式。
因此,确保所有引用的 Secret 在 ServiceAccount 中被正确指定是至关重要的。
类别:变更。
StorageObjectInUseProtection
插件将 kubernetes.io/pvc-protection
或
kubernetes.io/pv-protection
终结器(finalizers)添加到新创建的持久卷申领(PVC)
或持久卷(PV)中。如果用户尝试删除 PVC/PV,除非 PVC/PV 的保护控制器移除终结器,
否则 PVC/PV 不会被删除。有关更多详细信息,
请参考保护使用中的存储对象。
类别:变更。
该准入控制器为新创建的节点添加 NotReady
和 NoSchedule
污点。
这些污点能够避免一些竞态条件的发生,而这类竞态条件可能导致 Pod
在更新节点污点以准确反映其所报告状况之前,就被调度到新节点上。
类别:验证。
此准入控制器针对传入的匹配请求实现
CEL 校验。当 validatingadmissionpolicy
和 admissionregistration.k8s.io/v1alpha1
特性门控组/版本被启用时,
此特性被启用。如果任意 ValidatingAdmissionPolicy 失败,则请求失败。
类别:验证。
此准入控制器调用与请求匹配的所有验证性 Webhook。
匹配的 Webhook 将被并行调用。如果其中任何一个拒绝请求,则整个请求将失败。
该准入控制器仅在验证(Validating)阶段运行;与 MutatingAdmissionWebhook
准入控制器所调用的 Webhook 相反,它调用的 Webhook 不可以变更对象。
如果以此方式调用的 Webhook 有其它副作用(如:减少配额),则它必须具有协调机制。 这是因为无法保证后续的 Webhook 或其他验证性准入控制器都允许请求完成。
如果你禁用了 ValidatingAdmissionWebhook,还必须通过 --runtime-config
标志来禁用
admissionregistration.k8s.io/v1
组/版本中的 ValidatingWebhookConfiguration
对象。
有。推荐使用的准入控制器默认情况下都处于启用状态
(请查看这里)。
因此,你无需显式指定它们。
你可以使用 --enable-admission-plugins
标志(顺序不重要)来启用默认设置以外的其他准入控制器。
除了内置的 admission 插件, 准入插件可以作为扩展独立开发,并以运行时所配置的 Webhook 的形式运行。 此页面描述了如何构建、配置、使用和监视准入 Webhook。
准入 Webhook 是一种用于接收准入请求并对其进行处理的 HTTP 回调机制。 可以定义两种类型的准入 Webhook, 即验证性质的准入 Webhook 和变更性质的准入 Webhook。 变更性质的准入 Webhook 会先被调用。它们可以修改发送到 API 服务器的对象以执行自定义的设置默认值操作。
在完成了所有对象修改并且 API 服务器也验证了所传入的对象之后, 验证性质的 Webhook 会被调用,并通过拒绝请求的方式来强制实施自定义的策略。
如果准入 Webhook 需要保证它们所看到的是对象的最终状态以实施某种策略。 则应使用验证性质的准入 Webhook,因为对象被修改性质 Webhook 看到之后仍然可能被修改。
准入 Webhook 本质上是集群控制平面的一部分。你应该非常谨慎地编写和部署它们。 如果你打算编写或者部署生产级准入 Webhook, 请阅读用户指南以获取相关说明。 在下文中,我们将介绍如何快速试验准入 Webhook。
确保启用 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook 控制器。 这里是一组推荐的准入控制器, 通常可以启用。
确保启用了 admissionregistration.k8s.io/v1
API。
请参阅 Kubernetes e2e 测试中的
Admission Webhook 服务器的实现。
Webhook 处理由 API 服务器发送的 AdmissionReview
请求,并且将其决定作为
AdmissionReview
对象以相同版本发送回去。
有关发送到 Webhook 的数据的详细信息,请参阅 Webhook 请求。
要获取来自 Webhook 的预期数据,请参阅 Webhook 响应。
示例准入 Webhook 服务器置 ClientAuth
字段为空,
默认为 NoClientCert
。这意味着 Webhook 服务器不会验证客户端的身份,认为其是 API 服务器。
如果你需要双向 TLS 或其他方式来验证客户端,
请参阅如何对 API 服务器进行身份认证。
e2e 测试中的 Webhook 服务器通过 deployment API 部署在 Kubernetes 集群中。该测试还将创建一个 Service 作为 Webhook 服务器的前端。 参见相关代码。
你也可以在集群外部署 Webhook。这样做需要相应地更新你的 Webhook 配置。
你可以通过 ValidatingWebhookConfiguration 或者 MutatingWebhookConfiguration 动态配置哪些资源要被哪些准入 Webhook 处理。
以下是一个 ValidatingWebhookConfiguration
示例,Mutating Webhook 配置与此类似。
有关每个配置字段的详细信息,请参阅 Webhook 配置部分。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "pod-policy.example.com"
webhooks:
- name: "pod-policy.example.com"
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
scope: "Namespaced"
clientConfig:
service:
namespace: "example-namespace"
name: "example-service"
caBundle: <CA_BUNDLE>
admissionReviewVersions: ["v1"]
sideEffects: None
timeoutSeconds: 5
你必须在以上示例中将 <CA_BUNDLE>
替换为一个有效的 CA 证书包,
这是一个用 PEM 编码的(字段值是 Base64 编码)CA 证书包,用于校验 Webhook 的服务器证书。
scope
字段指定是仅集群范围的资源(Cluster)还是名字空间范围的资源资源(Namespaced)将与此规则匹配。
*
表示没有范围限制。
当使用 clientConfig.service
时,服务器证书必须对 <svc_name>.<svc_namespace>.svc
有效。
Webhook 调用的默认超时是 10 秒,你可以设置 timeout
并建议对 Webhook 设置较短的超时时间。
如果 Webhook 调用超时,则根据 Webhook 的失败策略处理请求。
当一个 API 服务器收到与 rules
相匹配的请求时,
该 API 服务器将按照 clientConfig
中指定的方式向 Webhook 发送一个 admissionReview
请求。
创建 Webhook 配置后,系统将花费几秒钟使新配置生效。
如果你的 Webhook 需要身份验证,则可以将 API 服务器配置为使用基本身份验证、持有者令牌或证书来向 Webhook 提供身份证明。完成此配置需要三个步骤。
启动 API 服务器时,通过 --admission-control-config-file
参数指定准入控制配置文件的位置。
在准入控制配置文件中,指定 MutatingAdmissionWebhook 控制器和 ValidatingAdmissionWebhook 控制器应该读取凭据的位置。
凭证存储在 kubeConfig 文件中(是的,与 kubectl 使用的模式相同),因此字段名称为 kubeConfigFile
。
以下是一个准入控制配置文件示例:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionWebhook
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: WebhookAdmissionConfiguration
kubeConfigFile: "<path-to-kubeconfig-file>"
- name: MutatingAdmissionWebhook
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: WebhookAdmissionConfiguration
kubeConfigFile: "<path-to-kubeconfig-file>"
# 1.17 中被弃用,推荐使用 apiserver.config.k8s.io/v1
apiVersion: apiserver.k8s.io/v1alpha1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionWebhook
configuration:
# 1.17 中被弃用,推荐使用 apiserver.config.k8s.io/v1,kind = WebhookAdmissionConfiguration
apiVersion: apiserver.config.k8s.io/v1alpha1
kind: WebhookAdmission
kubeConfigFile: "<path-to-kubeconfig-file>"
- name: MutatingAdmissionWebhook
configuration:
# 1.17 中被弃用,推荐使用 apiserver.config.k8s.io/v1,kind = WebhookAdmissionConfiguration
apiVersion: apiserver.config.k8s.io/v1alpha1
kind: WebhookAdmission
kubeConfigFile: "<path-to-kubeconfig-file>"
有关 AdmissionConfiguration
的更多信息,请参见
AdmissionConfiguration (v1) reference。
有关每个配置字段的详细信息,请参见 Webhook 配置部分。
在 kubeConfig 文件中,提供证书凭据:
apiVersion: v1
kind: Config
users:
# name 应设置为服务的 DNS 名称或配置了 Webhook 的 URL 的主机名(包括端口)。
# 如果将非 443 端口用于服务,则在配置 1.16+ API 服务器时,该端口必须包含在名称中。
#
# 对于配置在默认端口(443)上与服务对话的 Webhook,请指定服务的 DNS 名称:
# - name: webhook1.ns1.svc
# user: ...
#
# 对于配置在非默认端口(例如 8443)上与服务对话的 Webhook,请在 1.16+ 中指定服务的 DNS 名称和端口:
# - name: webhook1.ns1.svc:8443
# user: ...
# 并可以选择仅使用服务的 DNS 名称来创建第二节,以与 1.15 API 服务器版本兼容:
# - name: webhook1.ns1.svc
# user: ...
#
# 对于配置为使用 URL 的 Webhook,请匹配在 Webhook 的 URL 中指定的主机(和端口)。
# 带有 `url: https://www.example.com` 的 Webhook:
# - name: www.example.com
# user: ...
#
# 带有 `url: https://www.example.com:443` 的 Webhook:
# - name: www.example.com:443
# user: ...
#
# 带有 `url: https://www.example.com:8443` 的 Webhook:
# - name: www.example.com:8443
# user: ...
#
- name: 'webhook1.ns1.svc'
user:
client-certificate-data: "<pem encoded certificate>"
client-key-data: "<pem encoded key>"
# `name` 支持使用 * 通配符匹配前缀段。
- name: '*.webhook-company.org'
user:
password: "<password>"
username: "<name>"
# '*' 是默认匹配项。
- name: '*'
user:
token: "<token>"
当然,你需要设置 Webhook 服务器来处理这些身份验证请求。
Webhook 发送 POST 请求时,请设置 Content-Type: application/json
并对 admission.k8s.io
API
组中的 AdmissionReview
对象进行序列化,将所得到的 JSON 作为请求的主体。
Webhook 可以在配置中的 admissionReviewVersions
字段指定可接受的 AdmissionReview
对象版本:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
admissionReviewVersions: ["v1", "v1beta1"]
创建 Webhook 配置时,admissionReviewVersions
是必填字段。
Webhook 必须支持至少一个当前和以前的 API 服务器都可以解析的 AdmissionReview
版本。
API 服务器将发送的是 admissionReviewVersions
列表中所支持的第一个 AdmissionReview
版本。
如果 API 服务器不支持列表中的任何版本,则不允许创建配置。
如果 API 服务器遇到以前创建的 Webhook 配置,并且不支持该 API 服务器知道如何发送的任何
AdmissionReview
版本,则调用 Webhook 的尝试将失败,并依据失败策略进行处理。
此示例显示了 AdmissionReview
对象中包含的数据,该数据用于请求更新 apps/v1
Deployment
的 scale
子资源:
apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
# 唯一标识此准入回调的随机 uid
uid: 705ab4f5-6393-11e8-b7cc-42010a800002
# 传入完全限定的 group/version/kind 对象
kind:
group: autoscaling
version: v1
kind: Scale
# 修改 resource 的完全限定 group/version/kind
resource:
group: apps
version: v1
resource: deployments
# subResource(如果请求是针对 subResource 的)
subResource: scale
# 在对 API 服务器的原始请求中,传入对象的标准 group/version/kind
# 仅当 Webhook 指定 `matchPolicy: Equivalent` 且将对 API 服务器的原始请求
# 转换为 Webhook 注册的版本时,这才与 `kind` 不同。
requestKind:
group: autoscaling
version: v1
kind: Scale
# 在对 API 服务器的原始请求中正在修改的资源的标准 group/version/kind
# 仅当 Webhook 指定了 `matchPolicy:Equivalent` 并且将对 API 服务器的原始请求转换为
# Webhook 注册的版本时,这才与 `resource` 不同。
requestResource:
group: apps
version: v1
resource: deployments
# subResource(如果请求是针对 subResource 的)
# 仅当 Webhook 指定了 `matchPolicy:Equivalent` 并且将对
# API 服务器的原始请求转换为该 Webhook 注册的版本时,这才与 `subResource` 不同。
requestSubResource: scale
# 被修改资源的名称
name: my-deployment
# 如果资源是属于名字空间(或者是名字空间对象),则这是被修改的资源的名字空间
namespace: my-namespace
# 操作可以是 CREATE、UPDATE、DELETE 或 CONNECT
operation: UPDATE
userInfo:
# 向 API 服务器发出请求的经过身份验证的用户的用户名
username: admin
# 向 API 服务器发出请求的经过身份验证的用户的 UID
uid: 014fbff9a07c
# 向 API 服务器发出请求的经过身份验证的用户的组成员身份
groups:
- system:authenticated
- my-admin-group
# 向 API 服务器发出请求的用户相关的任意附加信息
# 该字段由 API 服务器身份验证层填充,并且如果 webhook 执行了任何
# SubjectAccessReview 检查,则应将其包括在内。
extra:
some-key:
- some-value1
- some-value2
# object 是被接纳的新对象。
# 对于 DELETE 操作,它为 null。
object:
apiVersion: autoscaling/v1
kind: Scale
# oldObject 是现有对象。
# 对于 CREATE 和 CONNECT 操作,它为 null。
oldObject:
apiVersion: autoscaling/v1
kind: Scale
# options 包含要接受的操作的选项,例如 meta.k8s.io/v CreateOptions、UpdateOptions 或 DeleteOptions。
# 对于 CONNECT 操作,它为 null。
options:
apiVersion: meta.k8s.io/v1
kind: UpdateOptions
# dryRun 表示 API 请求正在以 `dryrun` 模式运行,并且将不会保留。
# 带有副作用的 Webhook 应该避免在 dryRun 为 true 时激活这些副作用。
# 有关更多详细信息,请参见 http://k8s.io/zh-cn/docs/reference/using-api/api-concepts/#make-a-dry-run-request
dryRun: False
Webhook 使用 HTTP 200 状态码、Content-Type: application/json
和一个包含 AdmissionReview
对象的 JSON 序列化格式来发送响应。该 AdmissionReview
对象与发送的版本相同,且其中包含的 response
字段已被有效填充。
response
至少必须包含以下字段:
uid
,从发送到 Webhook 的 request.uid
中复制而来allowed
,设置为 true
或 false
Webhook 允许请求的最简单响应示例:
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": true
}
}
Webhook 禁止请求的最简单响应示例:
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": false
}
}
当拒绝请求时,Webhook 可以使用 status
字段自定义 http 响应码和返回给用户的消息。
有关状态类型的详细信息,请参见
API 文档。
禁止请求的响应示例,它定制了向用户显示的 HTTP 状态码和消息:
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": false,
"status": {
"code": 403,
"message": "You cannot do this because it is Tuesday and your name starts with A"
}
}
}
当允许请求时,mutating准入 Webhook 也可以选择修改传入的对象。
这是通过在响应中使用 patch
和 patchType
字段来完成的。
当前唯一支持的 patchType
是 JSONPatch
。
有关更多详细信息,请参见 JSON patch。
对于 patchType: JSONPatch
,patch
字段包含一个以 base64 编码的 JSON patch 操作数组。
例如,设置 spec.replicas
的单个补丁操作将是
[{"op": "add", "path": "/spec/replicas", "value": 3}]
。
如果以 Base64 形式编码,结果将是
W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0=
因此,添加该标签的 Webhook 响应为:
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": true,
"patchType": "JSONPatch",
"patch": "W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0="
}
}
准入 Webhook 可以选择性地返回在 HTTP Warning
头中返回给请求客户端的警告消息,警告代码为 299。
警告可以与允许或拒绝的准入响应一起发送。
如果你正在实现返回一条警告的 Webhook,则:
超过 256 个字符的单条警告消息在返回给客户之前可能会被 API 服务器截断。 如果超过 4096 个字符的警告消息(来自所有来源),则额外的警告消息会被忽略。
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": true,
"warnings": [
"duplicate envvar entries specified with name MY_ENV",
"memory request less than 4MB specified for container mycontainer, which will not start successfully"
]
}
}
要注册准入 Webhook,请创建 MutatingWebhookConfiguration
或 ValidatingWebhookConfiguration
API 对象。
MutatingWebhookConfiguration
或ValidatingWebhookConfiguration
对象的名称必须是有效的
DNS 子域名。
每种配置可以包含一个或多个 Webhook。如果在单个配置中指定了多个 Webhook,则应为每个 Webhook 赋予一个唯一的名称。 这是必需的,以使生成的审计日志和指标更易于与激活的配置相匹配。
每个 Webhook 定义以下内容。
每个 Webhook 必须指定用于确定是否应将对 apiserver 的请求发送到 Webhook 的规则列表。 每个规则都指定一个或多个 operations、apiGroups、apiVersions 和 resources 以及资源的 scope:
operations
列出一个或多个要匹配的操作。
可以是 CREATE
、UPDATE
、DELETE
、CONNECT
或 *
以匹配所有内容。
apiGroups
列出了一个或多个要匹配的 API 组。""
是核心 API 组。"*"
匹配所有 API 组。
apiVersions
列出了一个或多个要匹配的 API 版本。"*"
匹配所有 API 版本。
resources
列出了一个或多个要匹配的资源。
"*"
匹配所有资源,但不包括子资源。"*/*"
匹配所有资源,包括子资源。"pods/*"
匹配 pod 的所有子资源。"*/status"
匹配所有 status 子资源。scope
指定要匹配的范围。有效值为 "Cluster"
、"Namespaced"
和 "*"
。
子资源匹配其父资源的范围。默认值为 "*"
。
"Cluster"
表示只有集群作用域的资源才能匹配此规则(API 对象 Namespace 是集群作用域的)。"Namespaced"
意味着仅具有名字空间的资源才符合此规则。"*"
表示没有作用域限制。如果传入请求与任何 Webhook rules
的指定 operations
、groups
、versions
、
resources
和 scope
匹配,则该请求将发送到 Webhook。
以下是可用于指定应拦截哪些资源的规则的其他示例。
匹配针对 apps/v1
和 apps/v1beta1
组中 deployments
和 replicasets
资源的 CREATE
或 UPDATE
请求:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
...
webhooks:
- name: my-webhook.example.com
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["apps"]
apiVersions: ["v1", "v1beta1"]
resources: ["deployments", "replicasets"]
scope: "Namespaced"
...
匹配所有 API 组和版本中的所有资源(但不包括子资源)的创建请求:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
rules:
- operations: ["CREATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*"]
scope: "*"
匹配所有 API 组和版本中所有 status
子资源的更新请求:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
rules:
- operations: ["UPDATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*/status"]
scope: "*"
通过指定 objectSelector
,Webhook 能够根据可能发送的对象的标签来限制哪些请求被拦截。
如果指定,则将对 objectSelector
和可能发送到 Webhook 的 object 和 oldObject
进行评估。如果两个对象之一与选择算符匹配,则认为该请求已匹配。
空对象(对于创建操作而言为 oldObject
,对于删除操作而言为 newObject
),
或不能带标签的对象(例如 DeploymentRollback
或 PodProxyOptions
对象)
被认为不匹配。
仅当选择使用 Webhook 时才使用对象选择器,因为最终用户可以通过设置标签来 跳过准入 Webhook。
这个例子展示了一个变更性质的 Webhook,它将匹配带有标签 foo:bar
的所有资源(但不包括子资源)的
CREATE
操作:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
objectSelector:
matchLabels:
foo: bar
rules:
- operations: ["CREATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*"]
scope: "*"
有关标签选择算符的更多示例,请参见标签的概念。
通过指定 namespaceSelector
,
Webhook 可以根据具有名字空间的资源所处的名字空间的标签来选择拦截哪些资源的操作。
namespaceSelector
根据名字空间的标签是否匹配选择算符,决定是否针对具名字空间的资源
(或 Namespace 对象)的请求运行 Webhook。
如果对象是除 Namespace 以外的集群范围的资源,则 namespaceSelector
标签无效。
本例给出的变更性质的 Webhook 将匹配到对名字空间中具名字空间的资源的 CREATE
请求,
前提是这些资源不含值为 "0" 或 "1" 的 "runlevel" 标签:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
namespaceSelector:
matchExpressions:
- key: runlevel
operator: NotIn
values: ["0", "1"]
rules:
- operations: ["CREATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*"]
scope: "Namespaced"
此示例显示了一个验证性质的 Webhook,它将匹配到对某名字空间中的任何具名字空间的资源的
CREATE
请求,前提是该名字空间具有值为 "prod" 或 "staging" 的 "environment" 标签:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
namespaceSelector:
matchExpressions:
- key: environment
operator: In
values: ["prod", "staging"]
rules:
- operations: ["CREATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*"]
scope: "Namespaced"
有关标签选择算符的更多示例, 请参见标签的概念。
API 服务器可以通过多个 API 组或版本来提供对象。
例如,如果一个 Webhook 仅为某些 API 组/版本指定了规则(例如
apiGroups:["apps"], apiVersions:["v1","v1beta1"]
),而修改资源的请求是通过另一个
API 组/版本(例如 extensions/v1beta1
)发出的,该请求将不会被发送到 Webhook。
matchPolicy
允许 Webhook 定义如何使用其 rules
匹配传入的请求。
允许的值为 Exact
或 Equivalent
。
Exact
表示仅当请求与指定规则完全匹配时才应拦截该请求。Equivalent
表示如果某个请求意在修改 rules
中列出的资源,
即使该请求是通过其他 API 组或版本发起,也应拦截该请求。在上面给出的示例中,仅为 apps/v1
注册的 Webhook 可以使用 matchPolicy
:
matchPolicy: Exact
表示不会将 extensions/v1beta1
请求发送到 WebhookmatchPolicy:Equivalent
表示将 extensions/v1beta1
请求发送到 Webhook
(将对象转换为 Webhook 指定的版本:apps/v1
)建议指定 Equivalent
,确保升级后启用 API 服务器中资源的新版本时,
Webhook 继续拦截他们期望的资源。
当 API 服务器停止提供某资源时,该资源不再被视为等同于该资源的其他仍在提供服务的版本。
例如,extensions/v1beta1
中的 Deployment 已被废弃,计划在 v1.16 中移除。
移除后,带有 apiGroups:["extensions"], apiVersions:["v1beta1"], resources: ["deployments"]
规则的 Webhook 将不再拦截通过 apps/v1
API 来创建的 Deployment。
因此,Webhook 应该优先注册稳定版本的资源。
此示例显示了一个验证性质的 Webhook,该 Webhook 拦截对 Deployment 的修改(无论 API 组或版本是什么),
始终会发送一个 apps/v1
版本的 Deployment 对象:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
matchPolicy: Equivalent
rules:
- operations: ["CREATE","UPDATE","DELETE"]
apiGroups: ["apps"]
apiVersions: ["v1"]
resources: ["deployments"]
scope: "Namespaced"
准入 Webhook 所用的 matchPolicy
默认为 Equivalent
。
matchConditions
Kubernetes v1.30 [stable]
(enabled by default: true)如果你需要细粒度地过滤请求,你可以为 Webhook 定义匹配条件。
如果你发现匹配规则、objectSelectors
和 namespaceSelectors
仍然不能提供你想要的何时进行 HTTP
调用的过滤条件,那么添加这些条件会很有用。
匹配条件是 CEL 表达式。
所有匹配条件都必须为 true 才能调用 Webhook。
以下是一个例子,说明了匹配条件的几种不同用法:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
matchPolicy: Equivalent
rules:
- operations: ['CREATE','UPDATE']
apiGroups: ['*']
apiVersions: ['*']
resources: ['*']
failurePolicy: 'Ignore' # 失败时继续处理请求但跳过 Webhook (可选值)
sideEffects: None
clientConfig:
service:
namespace: my-namespace
name: my-webhook
caBundle: '<omitted>'
# 你可以为每个 Webhook 配置最多 64 个 matchConditions
matchConditions:
- name: 'exclude-leases' # 每个匹配条件必须有唯一的名称
expression: '!(request.resource.group == "coordination.k8s.io" && request.resource.resource == "leases")' # 匹配非租约资源
- name: 'exclude-kubelet-requests'
expression: '!("system:nodes" in request.userInfo.groups)' # 匹配非节点用户发出的请求
- name: 'rbac' # 跳过 RBAC 请求,该请求将由第二个 Webhook 处理
expression: 'request.resource.group != "rbac.authorization.k8s.io"'
# 这个示例演示了如何使用 “authorizer”。
# 授权检查比简单的表达式更复杂,因此在这个示例中,使用第二个 Webhook 来针对 RBAC 请求进行处理。
# 两个 Webhook 都可以由同一个端点提供服务。
- name: rbac.my-webhook.example.com
matchPolicy: Equivalent
rules:
- operations: ['CREATE','UPDATE']
apiGroups: ['rbac.authorization.k8s.io']
apiVersions: ['*']
resources: ['*']
failurePolicy: 'Fail' # 失败时拒绝请求 (默认值)
sideEffects: None
clientConfig:
service:
namespace: my-namespace
name: my-webhook
caBundle: '<omitted>'
# 你可以为每个 Webhook 配置最多 64 个 matchConditions
matchConditions:
- name: 'breakglass'
# 跳过由授权给 “breakglass” 的用户在这个 Webhook 上发起的请求。
# “breakglass” API 不需要在这个检查之外存在。
expression: '!authorizer.group("admissionregistration.k8s.io").resource("validatingwebhookconfigurations").name("my-webhook.example.com").check("breakglass").allowed()'
你可以为每个 Webhook 在 matchConditions
字段中定义最多 64 个匹配条件。
匹配条件可以访问以下 CEL 变量:
object
- 来自传入请求的对象。对于 DELETE 请求,该值为 null。
该对象版本可能根据 matchPolicy 进行转换。oldObject
- 现有对象。对于 CREATE 请求,该值为 null。request
- AdmissionReview 的请求部分,不包括 object 和 oldObject。authorizer
- 一个 CEL 鉴权组件。可用于对请求的主体(经过身份认证的用户)执行鉴权检查。
更多详细信息,请参阅 Kubernetes CEL 库文档中的
Authz。authorizer.requestResource
- 对配置的请求资源(组、资源、(子资源)、名字空间、名称)进行授权检查的快捷方式。了解有关 CEL 表达式的更多信息,请参阅 Kubernetes 参考文档中的通用表达式语言。
如果在对匹配条件求值时出现错误,则不会调用 Webhook。根据以下方式确定是否拒绝请求:
false
(不管其他错误),API 服务器将跳过 Webhook。failurePolicy: Fail
,拒绝请求(不调用 Webhook)。failurePolicy: Ignore
,继续处理请求但跳过 Webhook。API 服务器确定请求应发送到 Webhook 后,它需要知道如何调用 Webhook。
此信息在 Webhook 配置的 clientConfig
节中指定。
Webhook 可以通过 URL 或服务引用来调用,并且可以选择包含自定义 CA 包,以用于验证 TLS 连接。
url
以标准 URL 形式给出 Webhook 的位置(scheme://host:port/path
)。
host
不应引用集群中运行的服务;通过指定 service
字段来使用服务引用。
主机可以通过某些 API 服务器中的外部 DNS 进行解析。
(例如,kube-apiserver
无法解析集群内 DNS,因为这将违反分层规则)。host
也可以是 IP 地址。
请注意,将 localhost
或 127.0.0.1
用作 host
是有风险的,
除非你非常小心地在所有运行 apiserver 的、可能需要对此 Webhook
进行调用的主机上运行。这样的安装方式可能不具有可移植性,即很难在新集群中启用。
scheme 必须为 "https";URL 必须以 "https://" 开头。
使用用户或基本身份验证(例如:user:password@
)是不允许的。
使用片段(#...
)和查询参数(?...
)也是不允许的。
这是配置为调用 URL 的变更性质的 Webhook 的示例 (并且期望使用系统信任根证书来验证 TLS 证书,因此不指定 caBundle):
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
clientConfig:
url: "https://my-webhook.example.com:9443/my-webhook-path"
clientConfig
内部的 Service 是对该 Webhook 服务的引用。
如果 Webhook 在集群中运行,则应使用 service
而不是 url
。
服务的 namespace
和 name
是必需的。
port
是可选的,默认值为 443。path
是可选的,默认为 "/"。
这是一个 mutating Webhook 的示例,该 mutating Webhook 配置为在子路径 "/my-path" 端口
"1234" 上调用服务,并使用自定义 CA 包针对 ServerName
my-service-name.my-service-namespace.svc
验证 TLS 连接:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
clientConfig:
caBundle: <CA_BUNDLE>
service:
namespace: my-service-namespace
name: my-service-name
path: /my-path
port: 1234
你必须在以上示例中将 <CA_BUNDLE>
替换为一个有效的 VA 证书包,
这是一个用 PEM 编码的 CA 证书包,用于校验 Webhook 的服务器证书。
Webhook 通常仅对发送给他们的 AdmissionReview
内容进行操作。
但是,某些 Webhook 在处理 admission 请求时会进行带外更改。
进行带外更改的(产生“副作用”的)Webhook 必须具有协调机制(如控制器), 该机制定期确定事物的实际状态,并调整由准入 Webhook 修改的带外数据以反映现实情况。 这是因为对准入 Webhook 的调用不能保证所准入的对象将原样保留,或根本不保留。 以后,Webhook 可以修改对象的内容,在写入存储时可能会发生冲突, 或者服务器可以在持久保存对象之前关闭电源。
此外,处理 dryRun: true
admission 请求时,具有副作用的 Webhook 必须避免产生副作用。
一个 Webhook 必须明确指出在使用 dryRun
运行时不会有副作用,
否则 dry-run
请求将不会发送到该 Webhook,而 API 请求将会失败。
Webhook 使用 Webhook 配置中的 sideEffects
字段显示它们是否有副作用:
None
:调用 Webhook 没有副作用。NoneOnDryRun
:调用 Webhook 可能会有副作用,但是如果将带有 dryRun: true
属性的请求发送到 Webhook,则 Webhook 将抑制副作用(该 Webhook 可识别 dryRun
)。这是一个验证性质的 Webhook 的示例,表明它对 dryRun: true
请求没有副作用:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
sideEffects: NoneOnDryRun
由于 Webhook 会增加 API 请求的延迟,因此应尽快完成自身的操作。
timeoutSeconds
用来配置在将调用视为失败之前,允许 API 服务器等待 Webhook 响应的时间长度。
如果超时在 Webhook 响应之前被触发,则基于失败策略,将忽略 Webhook 调用或拒绝 API 调用。
超时值必须设置在 1 到 30 秒之间。
这是一个自定义超时设置为 2 秒的 validating Webhook 的示例:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
timeoutSeconds: 2
准入 Webhook 所用的超时时间默认为 10 秒。
变更性质的准入插件(包括 Webhook)的任何一种排序方式都不会适用于所有情况。
(参见 https://issue.k8s.io/64333 示例)。
变更性质的 Webhook 可以向对象中添加新的子结构(例如向 pod
中添加 container
),
已经运行的其他修改插件可能会对这些新结构有影响
(就像在所有容器上设置 imagePullPolicy
一样)。
要允许变更性质的准入插件感应到其他插件所做的更改,
如果变更性质的 Webhook 修改了一个对象,则会重新运行内置的变更性质的准入插件,
并且变更性质的 Webhook 可以指定 reinvocationPolicy
来控制是否也重新调用它们。
可以将 reinvocationPolicy
设置为 Never
或 IfNeeded
。 默认为 Never
。
Never
: 在一次准入测试中,不得多次调用 Webhook。IfNeeded
: 如果在最初的 Webhook 调用之后被其他对象的插件修改了被接纳的对象,
则可以作为准入测试的一部分再次调用该 Webhook。要注意的重要因素有:
这是一个变更性质的 Webhook 的示例,该 Webhook 在以后的准入插件修改对象时被重新调用:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
reinvocationPolicy: IfNeeded
变更性质的 Webhook 必须具有幂等性, 并且能够成功处理已被接纳并可能被修改的对象的变更性质的 Webhook。 对于所有变更性质的准入 Webhook 都是如此, 因为它们可以在对象中进行的任何更改可能已经存在于用户提供的对象中,但是对于选择重新调用的 Webhook 来说是必不可少的。
failurePolicy
定义了如何处理准入 Webhook 中无法识别的错误和超时错误。允许的值为 Ignore
或 Fail
。
Ignore
表示调用 Webhook 的错误将被忽略并且允许 API 请求继续。Fail
表示调用 Webhook 的错误导致准入失败并且 API 请求被拒绝。这是一个变更性质的 Webhook,配置为在调用准入 Webhook 遇到错误时拒绝 API 请求:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
failurePolicy: Fail
准入 Webhook 所用的默认 failurePolicy
是 Fail
。
API 服务器提供了监视准入 Webhook 行为的方法。这些监视机制可帮助集群管理员回答以下问题:
有时,了解 API 请求中的哪个变更性质的 Webhook 使对象改变以及该 Webhook 应用了哪些更改很有用。
Kubernetes API 服务器针对每个变更性质的 Webhook 调用执行审计操作。 每个调用都会生成一个审计注解,记述请求对象是否发生改变, 可选地还可以根据 Webhook 的准入响应生成一个注解,记述所应用的修补。 针对给定请求的给定执行阶段,注解被添加到审计事件中, 然后根据特定策略进行预处理并写入后端。
事件的审计级别决定了要记录哪些注解:
在 Metadata
或更高审计级别上,将使用 JSON 负载记录带有键名
mutation.webhook.admission.k8s.io/round_{round idx}_index_{order idx}
的注解,
该注解表示针对给定请求调用了 Webhook,以及该 Webhook 是否更改了对象。
例如,对于正在被重新调用的某 Webhook,所记录的注解如下。 Webhook 在 mutating Webhook 链中排在第三个位置,并且在调用期间未改变请求对象。
# 审计事件相关记录
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"annotations": {
"mutation.webhook.admission.k8s.io/round_1_index_2": "{\"configuration\":\"my-mutating-webhook-configuration.example.com\",\"webhook\":\"my-webhook.example.com\",\"mutated\": false}"
# 其他注解
...
}
# 其他字段
...
}
# 反序列化的注解值
{
"configuration": "my-mutating-webhook-configuration.example.com",
"webhook": "my-webhook.example.com",
"mutated": false
}
对于在第一轮中调用的 Webhook,所记录的注解如下。 Webhook 在变更性质的 Webhook 链中排在第一位,并在调用期间改变了请求对象。
# 审计事件相关记录
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"annotations": {
"mutation.webhook.admission.k8s.io/round_0_index_0": "{\"configuration\":\"my-mutating-webhook-configuration.example.com\",\"webhook\":\"my-webhook-always-mutate.example.com\",\"mutated\": true}"
# 其他注解
...
}
# 其他字段
...
}
# 反序列化的注解值
{
"configuration": "my-mutating-webhook-configuration.example.com",
"webhook": "my-webhook-always-mutate.example.com",
"mutated": true
}
在 Request
或更高审计级别上,将使用 JSON 负载记录带有键名为
patch.webhook.admission.k8s.io/round_{round idx}_index_{order idx}
的注解,
该注解表明针对给定请求调用了 Webhook 以及应用于请求对象之上的修改。
例如,以下是针对正在被重新调用的某 Webhook 所记录的注解。 Webhook 在变更性质的 Webhook 链中排在第四,并在其响应中包含一个 JSON 补丁, 该补丁已被应用于请求对象。
# 审计事件相关记录
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"annotations": {
"patch.webhook.admission.k8s.io/round_1_index_3": "{\"configuration\":\"my-other-mutating-webhook-configuration.example.com\",\"webhook\":\"my-webhook-always-mutate.example.com\",\"patch\":[{\"op\":\"add\",\"path\":\"/data/mutation-stage\",\"value\":\"yes\"}],\"patchType\":\"JSONPatch\"}"
# 其他注解
...
}
# 其他字段
...
}
# 反序列化的注解值
{
"configuration": "my-other-mutating-webhook-configuration.example.com",
"webhook": "my-webhook-always-mutate.example.com",
"patchType": "JSONPatch",
"patch": [
{
"op": "add",
"path": "/data/mutation-stage",
"value": "yes"
}
]
}
API 服务器从 /metrics
端点公开 Prometheus 指标,这些指标可用于监控和诊断 API 服务器状态。
以下指标记录了与准入 Webhook 相关的状态。
有时,了解哪些准入 Webhook 经常拒绝 API 请求以及拒绝的原因是很有用的。
在 v1.16+ 中,kube-apiserver 提供了 Prometheus 计数器度量值,记录 准入 Webhook 的拒绝次数。 度量值的标签给出了 Webhook 拒绝该请求的原因:
name
:拒绝请求 Webhook 的名称。operation
:请求的操作类型可以是 CREATE
、UPDATE
、DELETE
和 CONNECT
其中之一。type
:Admission Webhook 类型,可以是 admit
和 validating
其中之一。error_type
:标识在 Webhook 调用期间是否发生了错误并且导致了拒绝。其值可以是以下之一:calling_webhook_error
:发生了来自准入 Webhook 的无法识别的错误或超时错误,
并且 Webhook 的 失败策略 设置为 Fail
。no_error
:未发生错误。Webhook 在准入响应中以 allowed: false
值拒绝了请求。
度量标签 rejection_code
记录了在准入响应中设置的 .status.code
。apiserver_internal_error
:apiserver 发生内部错误。rejection_code
:当 Webhook 拒绝请求时,在准入响应中设置的 HTTP 状态码。拒绝计数指标示例:
# HELP apiserver_admission_webhook_rejection_count [ALPHA] Admission webhook rejection count, identified by name and broken out for each admission type (validating or admit) and operation. Additional labels specify an error type (calling_webhook_error or apiserver_internal_error if an error occurred; no_error otherwise) and optionally a non-zero rejection code if the webhook rejects the request with an HTTP status code (honored by the apiserver when the code is greater or equal to 400). Codes greater than 600 are truncated to 600, to keep the metrics cardinality bounded.
# TYPE apiserver_admission_webhook_rejection_count counter
apiserver_admission_webhook_rejection_count{error_type="calling_webhook_error",name="always-timeout-webhook.example.com",operation="CREATE",rejection_code="0",type="validating"} 1
apiserver_admission_webhook_rejection_count{error_type="calling_webhook_error",name="invalid-admission-response-webhook.example.com",operation="CREATE",rejection_code="0",type="validating"} 1
apiserver_admission_webhook_rejection_count{error_type="no_error",name="deny-unwanted-configmap-data.example.com",operation="CREATE",rejection_code="400",type="validating"} 13
幂等的变更性质的准入 Webhook 能够成功处理已经被它接纳甚或修改的对象。 即使多次执行该准入测试,也不会产生与初次执行结果相异的结果。
CREATE
Pod 请求,将 Pod 的字段 .spec.securityContext.runAsNonRoot
设置为 true,以实施安全最佳实践。CREATE
Pod 请求,如果未设置容器的字段
.spec.containers[].resources.limits
,设置默认资源限制值。CREATE
Pod 请求,如果 Pod 中不存在名为 foo-sidecar
的边车容器,
向 Pod 注入一个 foo-sidecar
容器。在上述情况下,可以安全地重新调用 Webhook,或接受已经设置了字段的对象。
CREATE
Pod 请求,注入名称为 foo-sidecar
并带有当前时间戳的边车容器(例如 foo-sidecar-19700101-000000
)。CREATE/UPDATE
Pod 请求,如果容器已设置标签 "env"
则拒绝,
否则将 "env": "prod"
标签添加到容器。CREATE
Pod 请求,盲目地添加一个名为 foo-sidecar
的边车容器,
而未查看 Pod 中是否已经有 foo-sidecar
容器。在上述第一种情况下,重新调用该 Webhook 可能导致同一个边车容器多次注入到 Pod 中,而且每次使用不同的容器名称。 类似地,如果 Sidecar 已存在于用户提供的 Pod 中,则 Webhook 可能注入重复的容器。
在上述第二种情况下,重新调用 Webhook 将导致 Webhook 自身输出失败。
在上述第三种情况下,重新调用 Webhook 将导致 Pod 规约中的容器重复, 从而使请求无效并被 API 服务器拒绝。
建议通过将 .webhooks[].matchPolicy
设置为 Equivalent
,
以确保准入 Webhooks 始终拦截对象的所有版本。
建议准入 Webhooks 应该更偏向注册资源的稳定版本。
如果无法拦截对象的所有版本,可能会导致准入策略未再某些版本的请求上执行。
有关示例,请参见匹配请求:matchPolicy。
建议准入 Webhook 尽快完成执行(时长通常是毫秒级),因为它们会增加 API 请求的延迟。 建议对 Webhook 使用较小的超时值。有关更多详细信息,请参见超时。
建议准入 Webhook 应该采用某种形式的负载均衡机制,以提供高可用性和高性能。 如果集群中正在运行 Webhook,则可以在服务后面运行多个 Webhook 后端,以利用该服务支持的负载均衡。
如果某准入 Webhook 需要保证自己能够看到对象的最终状态以实施策略, 则应该使用一个验证性质的 Webhook, 因为可以通过变更性质的 Webhook 看到对象后对其进行修改。
例如,一个变更性质的准入 Webhook 被配置为在每个 CREATE
Pod
请求中注入一个名称为 "foo-sidecar" 的边车容器。
如果必须存在边车容器,则还应配置一个验证性质的准入 Webhook 以拦截
CREATE
Pod 请求,并验证要创建的对象中是否存在具有预期配置的名称为
"foo-sidecar" 的容器。
如果集群内的 Webhook 配置能够拦截启动其自己的 Pod 所需的资源, 则该 Webhook 可能导致其自身部署时发生死锁。
例如,某变更性质的准入 Webhook 配置为仅当 Pod 中设置了某个标签
(例如 "env": "prod"
)时,才接受 CREATE
Pod 请求。
Webhook 服务器在未设置 "env"
标签的 Deployment 中运行。当运行 Webhook 服务器的
容器的节点运行不正常时,Webhook 部署尝试将容器重新调度到另一个节点。
但是,由于未设置 "env"
标签,因此请求将被现有的 Webhook 服务器拒绝,并且调度迁移不会发生。
建议使用 namespaceSelector 排除 Webhook 所在的名字空间。
建议准入 Webhook 应尽可能避免副作用,这意味着该准入 Webhook 仅对发送给他们的
AdmissionReview
的内容起作用,并且不要进行额外更改。
如果 Webhook 没有任何副作用,则 .webhooks[].sideEffects
字段应设置为
None
。
如果在准入执行期间存在副作用,则应在处理 dryRun
为 true
的 AdmissionReview
对象时避免产生副作用,并且其 .webhooks[].sideEffects
字段应设置为
NoneOnDryRun
。更多详细信息,请参见副作用。
kube-system
名字空间包含由 Kubernetes 系统创建的对象,
例如用于控制平面组件的服务账号,诸如 kube-dns
之类的 Pod 等。
意外更改或拒绝 kube-system
名字空间中的请求可能会导致控制平面组件停止运行或者导致未知行为发生。
如果你的准入 Webhook 不想修改 Kubernetes 控制平面的行为,请使用
namespaceSelector
避免拦截 kube-system
名字空间。
ServiceAccount 为 Pod 中运行的进程提供了一个身份。
Pod 内的进程可以使用其关联服务账号的身份,向集群的 API 服务器进行身份认证。
有关服务账号的介绍, 请参阅配置服务账号。
本任务指南阐述有关 ServiceAccount 的几个概念。 本指南还讲解如何获取或撤销代表 ServiceAccount 的令牌。
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
为了能够准确地跟随这些步骤,确保你有一个名为 examplens
的名字空间。
如果你没有,运行以下命令创建一个名字空间:
kubectl create namespace examplens
Kubernetes 区分用户账号和服务账号的概念,主要基于以下原因:
ServiceAccount 令牌可以被绑定到 kube-apiserver 中存在的 API 对象。 这可用于将令牌的有效性与另一个 API 对象的存在与否关联起来。 支持的对象类型如下:
当将令牌绑定到某对象时,该对象的 metadata.name
和 metadata.uid
将作为额外的“私有声明”存储在所发布的 JWT 中。
当将被绑定的令牌提供给 kube-apiserver 时,服务帐户身份认证组件将提取并验证这些声明。
如果所引用的对象或 ServiceAccount 正处于删除中(例如,由于 finalizer 的原因),
那么在 .metadata.deletionTimestamp
时间戳之后的 60 秒(或更长时间)后的某一时刻,
使用该令牌进行身份认证将会失败。
如果所引用的对象不再存在(或其 metadata.uid
不匹配),则请求将无法通过认证。
Kubernetes v1.30 [beta]
(enabled by default: true)当服务帐户令牌被绑定到某 Pod 对象时,一些额外的元数据也会被嵌入到令牌中,
包括所绑定 Pod 的 spec.nodeName
字段的值以及该节点的 uid(如果可用)。
当使用令牌进行身份认证时,kube-apiserver 不会检查此节点信息的合法性。 由于节点信息被包含在令牌内,所以集成商在检查 JWT 时不必获取 Pod 或 Node API 对象来检查所关联的 Node 名称和 uid。
TokenReview
API 可用于校验并从令牌中提取私有声明:
test-pod
的 Pod 和一个名为 my-sa
的服务帐户。kubectl create token my-sa --bound-object-kind="Pod" --bound-object-name="test-pod"
tokenreview.yaml
的新文件中:apiVersion: authentication.k8s.io/v1
kind: TokenReview
spec:
token: <来自第二步的令牌内容>
kubectl create -o yaml -f tokenreview.yaml # 我们使用 '-o yaml' 以便检视命令输出
你应该看到如下所示的输出:
apiVersion: authentication.k8s.io/v1
kind: TokenReview
metadata:
creationTimestamp: null
spec:
token: <token>
status:
audiences:
- https://kubernetes.default.svc.cluster.local
authenticated: true
user:
extra:
authentication.kubernetes.io/credential-id:
- JTI=7ee52be0-9045-4653-aa5e-0da57b8dccdc
authentication.kubernetes.io/node-name:
- kind-control-plane
authentication.kubernetes.io/node-uid:
- 497e9d9a-47aa-4930-b0f6-9f2fb574c8c6
authentication.kubernetes.io/pod-name:
- test-pod
authentication.kubernetes.io/pod-uid:
- e87dbbd6-3d7e-45db-aafb-72b24627dff5
groups:
- system:serviceaccounts
- system:serviceaccounts:default
- system:authenticated
uid: f8b4161b-2e2b-11e9-86b7-2afc33b31a7e
username: system:serviceaccount:default:my-sa
尽管你使用了 kubectl create -f
来创建此资源,并与 Kubernetes
中的其他资源类型类似的方式定义它,但 TokenReview 是一种特殊类别,
kube-apiserver 实际上并不将 TokenReview 对象持久保存到 etcd 中。
因此 kubectl get tokenreview
不是一个有效的命令。
Kubernetes v1.22 [stable]
(enabled by default: true)默认情况下,Kubernetes 控制平面(特别是 ServiceAccount 准入控制器) 添加一个投射卷到 Pod, 此卷包括了访问 Kubernetes API 的令牌。
以下示例演示如何查找已启动的 Pod:
...
- name: kube-api-access-<随机后缀>
projected:
sources:
- serviceAccountToken:
path: token # 必须与应用所预期的路径匹配
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
该清单片段定义了由三个数据源组成的投射卷。在当前场景中,每个数据源也代表该卷内的一条独立路径。这三个数据源是:
serviceAccountToken
数据源,包含 kubelet 从 kube-apiserver 获取的令牌。
kubelet 使用 TokenRequest API 获取有时间限制的令牌。为 TokenRequest 服务的这个令牌会在
Pod 被删除或定义的生命周期(默认为 1 小时)结束之后过期。该令牌绑定到特定的 Pod,
并将其 audience(受众)设置为与 kube-apiserver
的 audience 相匹配。
这种机制取代了之前基于 Secret 添加卷的机制,之前 Secret 代表了针对 Pod 的 ServiceAccount 但不会过期。configMap
数据源。ConfigMap 包含一组证书颁发机构数据。
Pod 可以使用这些证书来确保自己连接到集群的 kube-apiserver(而不是连接到中间件或意外配置错误的对等点上)。downwardAPI
数据源,用于查找包含 Pod 的名字空间的名称,
并使该名称信息可用于在 Pod 内运行的应用程序代码。Pod 内挂载这个特定卷的所有容器都可以访问上述信息。
没有特定的机制可以使通过 TokenRequest 签发的令牌无效。 如果你不再信任为某个 Pod 绑定的服务账号令牌, 你可以删除该 Pod。删除 Pod 将使其绑定的服务账号令牌过期。
v1.22 之前的 Kubernetes 版本会自动创建凭据访问 Kubernetes API。 这种更老的机制基于先创建令牌 Secret,然后将其挂载到正运行的 Pod 中。
在包括 Kubernetes v1.31 在内最近的几个版本中,使用 TokenRequest API 直接获得 API 凭据, 并使用投射卷挂载到 Pod 中。使用这种方法获得的令牌具有绑定的生命周期, 当挂载的 Pod 被删除时这些令牌将自动失效。
你仍然可以手动创建 Secret 来保存服务账号令牌;例如在你需要一个永不过期的令牌的时候。
一旦你手动创建一个 Secret 并将其关联到 ServiceAccount, Kubernetes 控制平面就会自动将令牌填充到该 Secret 中。
尽管存在手动创建长久 ServiceAccount 令牌的机制,但还是推荐使用 TokenRequest 获得短期的 API 访问令牌。
在 1.24 版本之前,Kubernetes 自动为 ServiceAccount 生成基于 Secret 的令牌。
为了区分自动生成的令牌和手动创建的令牌,Kubernetes 会检查 ServiceAccount 的
Secret 字段是否有引用。如果该 Secret 被 secrets
字段引用,
它被视为自动生成的传统令牌。否则,它被视为手动创建的传统令牌。例如:
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-robot
namespace: default
secrets:
- name: build-robot-secret # 对于手动生成的令牌通常不会存在此字段
从 1.29 版本开始,如果传统 ServiceAccount 令牌在一定时间段(默认设置为一年)内未被使用,则会被标记为无效。 在定义的时间段(同样默认为一年)持续未被使用的令牌将由控制平面自动清除。
如果用户使用一个无效的自动生成的令牌,令牌验证器将执行以下操作:
authentication.k8s.io/legacy-token-invalidated: <secret name>/<namespace>
添加审计注解,invalid_legacy_auto_token_uses_total
指标计数加一,kubernetes.io/legacy-token-last-used
为新日期,当收到这个校验报错时,用户可以通过移除 kubernetes.io/legacy-token-invalid-since
标签更新 Secret,以临时允许使用此令牌。
以下是一个自动生成的传统令牌示例,它被标记了 kubernetes.io/legacy-token-last-used
和 kubernetes.io/legacy-token-invalid-since
标签:
apiVersion: v1
kind: Secret
metadata:
name: build-robot-secret
namespace: default
labels:
kubernetes.io/legacy-token-last-used: 2022-10-24
kubernetes.io/legacy-token-invalid-since: 2023-10-25
annotations:
kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token
ServiceAccount 控制器管理名字空间内的 ServiceAccount,
并确保每个活跃的名字空间中都存在名为 default
的 ServiceAccount。
服务账号令牌控制器作为 kube-controller-manager
的一部分运行,以异步的形式工作。
其职责包括:
你必须通过 --service-account-private-key-file
标志为
kube-controller-manager
的令牌控制器传入一个服务账号私钥文件。
该私钥用于为所生成的服务账号令牌签名。同样地,你需要通过
--service-account-key-file
标志将对应的公钥通知给
kube-apiserver。公钥用于在身份认证过程中校验令牌。
对 Pod 的改动通过一个被称为准入控制器的插件来实现。 它是 API 服务器的一部分。当 Pod 被创建时,该准入控制器会同步地修改 Pod。 如果该插件处于激活状态(在大多数发行版中都是默认激活的),当 Pod 被创建时它会进行以下操作:
.spec.serviceAccountName
,
准入控制器为新来的 Pod 将 ServiceAccount 的名称设为 default
。default
ServiceAccount。automountServiceAccountToken
字段或 Pod 的
automountServiceAccountToken
字段都未显式设置为 false
:volumeMount
添加到 Pod 中的每个容器,
忽略已为 /var/run/secrets/kubernetes.io/serviceaccount
路径定义的卷挂载的所有容器。
对于 Linux 容器,此卷挂载在 /var/run/secrets/kubernetes.io/serviceaccount
;
在 Windows 节点上,此卷挂载在等价的路径上。imagePullSecrets
,则准入控制器添加 imagePullSecrets
,
并从 ServiceAccount
进行复制。Kubernetes v1.28 [stable]
(enabled by default: true)此控制器在 kube-system
命名空间中生成名为
kube-apiserver-legacy-service-account-token-tracking
的 ConfigMap。
这个 ConfigMap 记录了系统开始监视传统服务账号令牌的时间戳。
Kubernetes v1.30 [stable]
(enabled by default: true)传统 ServiceAccount 令牌清理器作为 kube-controller-manager
的一部分运行,
每 24 小时检查一次,查看是否有任何自动生成的传统 ServiceAccount
令牌在特定时间段内未被使用。如果有的话,清理器会将这些令牌标记为无效。
清理器的工作方式是首先检查控制平面创建的 ConfigMap(前提是启用了
LegacyServiceAccountTokenTracking
)。如果当前时间是 ConfigMap
所包含日期之后的特定时间段,清理器会遍历集群中的 Secret 列表,
并评估每个类型为 kubernetes.io/service-account-token
的 Secret。
如果一个 Secret 满足以下所有条件,清理器会将其标记为无效:
清理器通过向 Secret 添加名为 kubernetes.io/legacy-token-invalid-since
的标签,
并将此值设置为当前日期,来标记 Secret 为无效。
如果一个无效的 Secret 在特定时间段内未被使用,清理器将会删除它。
上述所有的特定时间段都默认为一年。集群管理员可以通过 kube-controller-manager
组件的 --legacy-service-account-token-clean-up-period
命令行参数来配置此值。
Kubernetes v1.22 [stable]
你使用 ServiceAccount 的 TokenRequest 子资源为该 ServiceAccount 获取有时间限制的令牌。 你不需要调用它来获取在容器中使用的 API 令牌, 因为 kubelet 使用投射卷对此进行了设置。
如果你想要从 kubectl
使用 TokenRequest API,
请参阅为 ServiceAccount 手动创建 API 令牌。
Kubernetes 控制平面(特别是 ServiceAccount 准入控制器)向 Pod 添加了一个投射卷, kubelet 确保该卷包含允许容器作为正确 ServiceAccount 进行身份认证的令牌。
(这种机制取代了之前基于 Secret 添加卷的机制,之前 Secret 代表了 Pod 所用的 ServiceAccount 但不会过期。)
以下示例演示如何查找已启动的 Pod:
...
- name: kube-api-access-<random-suffix>
projected:
defaultMode: 420 # 这个十进制数等同于八进制 0644
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
该清单片段定义了由三个数据源信息组成的投射卷。
serviceAccountToken
数据源,包含 kubelet 从 kube-apiserver 获取的令牌。
kubelet 使用 TokenRequest API 获取有时间限制的令牌。为 TokenRequest 服务的这个令牌会在
Pod 被删除或定义的生命周期(默认为 1 小时)结束之后过期。在令牌过期之前,kubelet 还会刷新该令牌。
该令牌绑定到特定的 Pod,并将其 audience(受众)设置为与 kube-apiserver
的 audience 相匹配。configMap
数据源。ConfigMap 包含一组证书颁发机构数据。
Pod 可以使用这些证书来确保自己连接到集群的 kube-apiserver(而不是连接到中间件或意外配置错误的对等点上)。downwardAPI
数据源。这个 downwardAPI
卷获得包含 Pod 的名字空间的名称,
并使该名称信息可用于在 Pod 内运行的应用程序代码。挂载此卷的 Pod 内的所有容器均可以访问上述信息。
只有令牌请求机制不合适,才需要创建长久的 API 令牌。 令牌请求机制提供有时间限制的令牌;因为随着这些令牌过期,它们对信息安全方面的风险也会降低。
要为 ServiceAccount 创建一个不过期、持久化的 API 令牌,
请创建一个类型为 kubernetes.io/service-account-token
的 Secret,
附带引用 ServiceAccount 的注解。控制平面随后生成一个长久的令牌,
并使用生成的令牌数据更新该 Secret。
以下是此类 Secret 的示例清单:
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: mysecretname
annotations:
kubernetes.io/service-account.name: myserviceaccount
若要基于此示例创建 Secret,运行以下命令:
kubectl -n examplens create -f https://k8s.io/examples/secret/serviceaccount/mysecretname.yaml
若要查看该 Secret 的详细信息,运行以下命令:
kubectl -n examplens describe secret mysecretname
输出类似于:
Name: mysecretname
Namespace: examplens
Labels: <none>
Annotations: kubernetes.io/service-account.name=myserviceaccount
kubernetes.io/service-account.uid=8a85c4c4-8483-11e9-bc42-526af7764f64
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1362 bytes
namespace: 9 bytes
token: ...
如果你在 examplens
名字空间中启动一个新的 Pod,它可以使用你刚刚创建的
myserviceaccount
service-account-token Secret。
不要在 ServiceAccount 的 secrets
字段中引用手动创建的 Secret。
否则,如果这些手动创建的 Secret 长时间未被使用将会被清理掉。
请参考清理自动生成的传统 ServiceAccount 令牌。
如果你知道 Secret 的名称且该 Secret 包含要移除的令牌:
kubectl delete secret name-of-secret
否则,先找到 ServiceAccount 所用的 Secret。
# 此处假设你已有一个名为 'examplens' 的名字空间
kubectl -n examplens get serviceaccount/example-automated-thing -o yaml
输出类似于:
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"name":"example-automated-thing","namespace":"examplens"}}
creationTimestamp: "2019-07-21T07:07:07Z"
name: example-automated-thing
namespace: examplens
resourceVersion: "777"
selfLink: /api/v1/namespaces/examplens/serviceaccounts/example-automated-thing
uid: f23fd170-66f2-4697-b049-e1e266b7f835
secrets:
- name: example-automated-thing-token-zyxwv
随后删除你现在知道名称的 Secret:
kubectl -n examplens delete secret/example-automated-thing-token-zyxwv
如果创建了一个 examplens
名字空间进行试验,你可以移除它:
kubectl delete namespace examplens
Kubernetes v1.19 [stable]
Kubernetes 证书和信任包(trust bundle)API 可以通过为 Kubernetes API 的客户端提供编程接口, 实现 X.509 凭据的自动化制备, 从而请求并获取证书颁发机构(CA)发布的 X.509 证书。
此外,Kubernetes 还对分发信任包提供了实验性(Alpha)支持。
Kubernetes v1.19 [stable]
CertificateSigningRequest (CSR)资源用来向指定的签名者申请证书签名, 在最终签名之前,申请可能被批准,也可能被拒绝。
CertificateSigningRequest 资源类型允许客户端基于签名请求申请发放 X.509 证书。
CertificateSigningRequest 对象在 spec.request
字段中包含一个 PEM 编码的 PKCS#10 签名请求。
CertificateSigningRequest 使用 spec.signerName
字段标示签名者(请求的接收方)。
注意,spec.signerName
在 certificates.k8s.io/v1
之后的 API 版本是必填项。
在 Kubernetes v1.22 和以后的版本,客户可以设置 spec.expirationSeconds
字段(可选)来为颁发的证书设定一个特定的有效期。该字段的最小有效值是 600
,也就是 10 分钟。
创建完成的 CertificateSigningRequest,要先通过批准,然后才能签名。
根据所选的签名者,CertificateSigningRequest
可能会被控制器自动批准。
否则,就必须人工批准,
人工批准可以使用 REST API(或 client-go),也可以执行 kubectl certificate approve
命令。
同样,CertificateSigningRequest 也可能被驳回,
这就相当于通知了指定的签名者,这个证书不能签名。
对于已批准的证书,下一步是签名。
对应的签名控制器首先验证签名条件是否满足,然后才创建证书。
签名控制器然后更新 CertificateSigningRequest,
将新证书保存到现有 CertificateSigningRequest 对象的 status.certificate
字段中。
此时,字段 status.certificate
要么为空,要么包含一个用 PEM 编码的 X.509 证书。
直到签名完成前,CertificateSigningRequest 的字段 status.certificate
都为空。
一旦 status.certificate
字段完成填充,请求既算完成,
客户端现在可以从 CertificateSigningRequest 资源中获取已签名的证书的 PEM 数据。
当然如果不满足签名条件,签名者可以拒签。
为了减少集群中遗留的过时的 CertificateSigningRequest 资源的数量, 一个垃圾收集控制器将会周期性地运行。 此垃圾收集器会清除在一段时间内没有改变过状态的 CertificateSigningRequest:
授权创建 CertificateSigningRequest 和检索 CertificateSigningRequest:
create
、get
、list
、watch
,
group(组):certificates.k8s.io
,
resource(资源):certificatesigningrequests
例如:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-creator
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- create
- get
- list
- watch
授权批准 CertificateSigningRequest:
get
、list
、watch
,
group(组):certificates.k8s.io
,
resource(资源):certificatesigningrequests
update
,
group(组):certificates.k8s.io
,
resource(资源):certificatesigningrequests/approval
approve
,
group(组):certificates.k8s.io
,
resource(资源):signers
,
resourceName:<signerNameDomain>/<signerNamePath>
或 <signerNameDomain>/*
例如:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-approver
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- get
- list
- watch
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/approval
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- example.com/my-signer-name # example.com/* 可用于为 “example.com” 域中的所有签名者授权
verbs:
- approve
授权签名 CertificateSigningRequest:
get
、list
、watch
,
group(组):certificates.k8s.io
,
resource(资源):certificatesigningrequests
update
,
group(组):certificates.k8s.io
,
resource(资源):certificatesigningrequests/status
sign
,
group(组):certificates.k8s.io
,
resource(资源):signers
,
resourceName:<signerNameDomain>/<signerNamePath>
或 <signerNameDomain>/*
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-signer
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- get
- list
- watch
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/status
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- example.com/my-signer-name # example.com/* 可用于为 “example.com” 域中的所有签名者授权
verbs:
- sign
签名者抽象地代表可能签署或已签署安全证书的一个或多个实体。
任何要在特定集群以外提供的签名者都应该提供关于签名者工作方式的信息, 以便消费者可以理解这对于 CertifcateSigningRequest 和(如果启用的) ClusterTrustBundle 的意义。此类信息包括:
spec.expirationSeconds
字段指定等,
以及签名者决定的过期时间与 CSR spec.expirationSeconds
字段不同时的应对手段。一般来说,当 CSR 被批准通过,且证书被签名后,CertificateSigningRequest
的 status.certificate
字段将包含一个 PEM 编码的 X.509 证书。
有些签名者在 status.certificate
字段中存储多个证书。
在这种情况下,签名者的说明文档应当指明附加证书的含义。
例如,这是要在 TLS 握手时提供的证书和中继证书。
如果要让信任锚点(根证书)可用,应该将其与 CertificateSigningRequest 及其 status.certificate
字段分开处理。例如,你可以使用 ClusterTrustBundle。
PKCS#10 签名请求格式并没有一种标准的方法去设置证书的过期时间或者生命期,
因此,证书的过期时间或者生命期必须通过 CSR 对象的 spec.expirationSeconds
字段来设置。
当 spec.expirationSeconds
没有被指定时,内置的签名者默认使用 ClusterSigningDuration
配置选项
(kube-controller-manager 的命令行选项 --cluster-signing-duration
),该选项的默认值设为 1 年。
当 spec.expirationSeconds
被指定时,spec.expirationSeconds
和 ClusterSigningDuration
中的最小值会被使用。
spec.expirationSeconds
字段是在 Kubernetes v1.22 中加入的。早期的 Kubernetes 版本并不认识该字段。
v1.22 版本之前的 Kubernetes API 服务器会在创建对象的时候忽略该字段。
Kubernetes 提供了内置的签名者,每个签名者都有一个众所周知的 signerName
:
kubernetes.io/kube-apiserver-client
:签名的证书将被 API 服务器视为客户端证书,
kube-controller-manager 不会自动批准它。system:masters
的 CertificateSubjectRestriction 准入插件默认处于启用状态,
但它通常不是集群中唯一的集群管理员主体。["client auth"]
,但不能包含
["digital signature", "key encipherment", "client auth"]
之外的键。--cluster-signing-duration
选项和 CSR 对象的 spec.expirationSeconds
字段(如有设置该字段)中的最小值。kubernetes.io/kube-apiserver-client-kubelet
:签名的证书将被 kube-apiserver 视为客户端证书。
kube-controller-manager 可以自动批准它。
["system:nodes"]
,通用名称为 "system:node:${NODE_NAME}
" 开头["key encipherment", "digital signature", "client auth"]
或 ["digital signature", "client auth"]
。--cluster-signing-duration
选项和 CSR 对象的 spec.expirationSeconds
字段(如有设置该字段)中的最小值。kubernetes.io/kubelet-serving
:签名服务端证书,该服务证书被 API 服务器视为有效的 kubelet 服务端证书,
但没有其他保证。kube-controller-manager 不会自动批准它。["system:nodes"]
,通用名称为 "system:node:${NODE_NAME}
" 开头["key encipherment", "digital signature", "server auth"]
或 ["digital signature", "server auth"]
。--cluster-signing-duration
选项和 CSR 对象的 spec.expirationSeconds
字段(如有设置该字段)中的最小值。kubernetes.io/legacy-unknown
:不保证信任。Kubernetes 的一些第三方发行版可能会使用它签署的客户端证书。
稳定版的 CertificateSigningRequest API(certificates.k8s.io/v1
以及之后的版本)不允许将
signerName
设置为 kubernetes.io/legacy-unknown
。
kube-controller-manager 不会自动批准这类请求。--cluster-signing-duration
选项和 CSR 对象的 spec.expirationSeconds
字段(如有设置该字段)中的最小值。kube-controller-manager 为每个内置签名者实现了控制平面签名。 注意:所有这些故障仅在 kube-controller-manager 日志中报告。
spec.expirationSeconds
字段是在 Kubernetes v1.22 中加入的,早期的 Kubernetes 版本并不认识该字段,
v1.22 版本之前的 Kubernetes API 服务器会在创建对象的时候忽略该字段。
对于这些签名者,信任的分发发生在带外(out of band)。上述信任之外的任何信任都是完全巧合的。
例如,一些发行版可能会将 kubernetes.io/legacy-unknown
作为 kube-apiserver 的客户端证书,
但这个做法并不标准。
这些用途都没有以任何方式涉及到 ServiceAccount 中的 Secrets .data[ca.crt]
。
此 CA 证书包只保证使用默认的服务(kubernetes.default.svc
)来验证到 API 服务器的连接。
Kubernetes 控制平面实现了每一个 Kubernetes 签名者, 每个签名者的实现都是 kube-controller-manager 的一部分。
在 Kubernetes v1.18 之前, kube-controller-manager 签名所有标记为 approved 的 CSR。
spec.expirationSeconds
字段是在 Kubernetes v1.22 中加入的,早期的 Kubernetes 版本并不认识该字段,
v1.22 版本之前的 Kubernetes API 服务器会在创建对象的时候忽略该字段。
REST API 的用户可以通过向待签名的 CSR 的 status
子资源提交更新请求来对 CSR 进行签名。
作为这个请求的一部分,status.certificate
字段应设置为已签名的证书。
此字段可包含一个或多个 PEM 编码的证书。
所有的 PEM 块必须具备 "CERTIFICATE" 标签,且不包含文件头,且编码的数据必须是 RFC5280 第 4 节 中描述的 BER 编码的 ASN.1 证书结构。
证书内容示例:
-----BEGIN CERTIFICATE-----
MIIDgjCCAmqgAwIBAgIUC1N1EJ4Qnsd322BhDPRwmg3b/oAwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCeHgxCjAIBgNVBAgMAXgxCjAIBgNVBAcMAXgxCjAIBgNV
BAoMAXgxCjAIBgNVBAsMAXgxCzAJBgNVBAMMAmNhMRAwDgYJKoZIhvcNAQkBFgF4
MB4XDTIwMDcwNjIyMDcwMFoXDTI1MDcwNTIyMDcwMFowNzEVMBMGA1UEChMMc3lz
dGVtOm5vZGVzMR4wHAYDVQQDExVzeXN0ZW06bm9kZToxMjcuMC4wLjEwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDne5X2eQ1JcLZkKvhzCR4Hxl9+ZmU3
+e1zfOywLdoQxrPi+o4hVsUH3q0y52BMa7u1yehHDRSaq9u62cmi5ekgXhXHzGmm
kmW5n0itRECv3SFsSm2DSghRKf0mm6iTYHWDHzUXKdm9lPPWoSOxoR5oqOsm3JEh
Q7Et13wrvTJqBMJo1GTwQuF+HYOku0NF/DLqbZIcpI08yQKyrBgYz2uO51/oNp8a
sTCsV4OUfyHhx2BBLUo4g4SptHFySTBwlpRWBnSjZPOhmN74JcpTLB4J5f4iEeA7
2QytZfADckG4wVkhH3C2EJUmRtFIBVirwDn39GXkSGlnvnMgF3uLZ6zNAgMBAAGj
YTBfMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMB
Af8EAjAAMB0GA1UdDgQWBBTREl2hW54lkQBDeVCcd2f2VSlB1DALBgNVHREEBDAC
ggAwDQYJKoZIhvcNAQELBQADggEBABpZjuIKTq8pCaX8dMEGPWtAykgLsTcD2jYr
L0/TCrqmuaaliUa42jQTt2OVsVP/L8ofFunj/KjpQU0bvKJPLMRKtmxbhXuQCQi1
qCRkp8o93mHvEz3mTUN+D1cfQ2fpsBENLnpS0F4G/JyY2Vrh19/X8+mImMEK5eOy
o0BMby7byUj98WmcUvNCiXbC6F45QTmkwEhMqWns0JZQY+/XeDhEcg+lJvz9Eyo2
aGgPsye1o3DpyXnyfJWAWMhOz7cikS5X2adesbgI86PhEHBXPIJ1v13ZdfCExmdd
M1fLPhLyR54fGaY+7/X8P9AZzPefAkwizeXwe9ii6/a08vWoiE4=
-----END CERTIFICATE-----
非 PEM 内容可能会出现在证书 PEM 块前后的位置,且未经验证, 以允许使用 RFC7468 第 5.2 节中描述的解释性文本。
当使用 JSON 或 YAML 格式时,此字段是 base-64 编码。 包含上述示例证书的 CertificateSigningRequest 如下所示:
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
...
status:
certificate: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JS..."
签名者基于 CertificateSigningRequest 签发证书之前, 通常会检查 CSR 的签发是否已被批准。
kube-controller-manager 内建了一个证书批准者,其 signerName 为
kubernetes.io/kube-apiserver-client-kubelet
,
该批准者将 CSR 上用于节点凭据的各种权限委托给权威认证机构。
kube-controller-manager 将 SubjectAccessReview 资源发送(POST)到 API 服务器,
以便检验批准证书的授权。
kubectl
批准或驳回Kubernetes 管理员(拥有足够的权限)可以手工批准(或驳回)CertificateSigningRequest,
此操作使用 kubectl certificate approve
和 kubectl certificate deny
命令实现。
使用 kubectl 批准一个 CSR:
kubectl certificate approve <certificate-signing-request-name>
同样地,驳回一个 CSR:
kubectl certificate deny <certificate-signing-request-name>
REST API 的用户可以通过向待批准的 CSR 的 approval
子资源提交更新请求来批准 CSR。
例如,你可以编写一个
operator
来监视特定类型的 CSR,然后发送一个更新来批准它。
当你发出批准或驳回的指令时,根据你期望的状态来选择设置 Approved
或 Denied
。
批准(Approved
)的 CSR:
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
...
status:
conditions:
- lastUpdateTime: "2020-02-08T11:37:35Z"
lastTransitionTime: "2020-02-08T11:37:35Z"
message: Approved by my custom approver controller
reason: ApprovedByMyPolicy # 你可以将此字段设置为任意字符串
type: Approved
驳回(Denied
)的 CSR:
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
...
status:
conditions:
- lastUpdateTime: "2020-02-08T11:37:35Z"
lastTransitionTime: "2020-02-08T11:37:35Z"
message: Denied by my custom approver controller
reason: DeniedByMyPolicy # 你可以将此字段设置为任意字符串
type: Denied
status.conditions.reason
字段通常设置为一个首字母大写的对机器友好的原因码;
这是一个命名约定,但你也可以随你的个人喜好设置。
如果你想添加一个供人类使用的注释,那就用 status.conditions.message
字段。
Kubernetes v1.27 [alpha]
在 Kubernetes 1.31 中,如果想要使用此 API,
必须同时启用 ClusterTrustBundle
特性门控
以及 certificates.k8s.io/v1alpha1
API 组。
ClusterTrustBundle 是一个作用域为集群的对象,向集群内的对象分发 X.509 信任锚点(根证书)。 此对象旨在与 CertificateSigningRequest 中的签名者概念协同工作。
ClusterTrustBundle 可以使用两种模式: 签名者关联和签名者未关联。
所有 ClusterTrustBundle 对象都对其 trustBundle
字段的内容进行强大的验证。
该字段必须包含一个或多个经 DER 序列化的 X.509 证书,每个证书都封装在 PEM CERTIFICATE
块中,
这些证书必须解析为有效的 X.509 证书。
诸如块间数据和块内标头之类的 PEM 特性在对象验证期间要么被拒绝,要么可能被对象的消费者忽略。 此外,消费者被允许使用自己的任意但稳定的排序方式重新排序 bundle 中的证书。
ClusterTrustBundle 对象应该在集群内被视为全局可读的。 如果集群使用 RBAC 鉴权, 则所有 ServiceAccount 都具有默认授权,允许它们 get、list 和 watch 所有 ClusterTrustBundle 对象。如果你使用自己的鉴权机制,并且在集群中启用了 ClusterTrustBundle,则应设置等效规则以使这些对象在集群内公开,使这些对象按预期工作。
如果你没有默认在集群中列出集群信任包的权限,则可以扮演具有访问权限的 ServiceAccount, 以查看可用的 ClusterTrustBundle:
kubectl get clustertrustbundles --as='system:serviceaccount:mynamespace:default'
签名者关联的 ClusterTrustBundle 与签名者名称关联,例如:
apiVersion: certificates.k8s.io/v1alpha1
kind: ClusterTrustBundle
metadata:
name: example.com:mysigner:foo
spec:
signerName: example.com/mysigner
trustBundle: "<... PEM data ...>"
这些 ClusterTrustBundle 预期由集群中的特定签名者控制器维护,因此它们具有多个安全特性:
attest
API 组 certificates.k8s.io
;资源路径 signers
)。
你可以为特定资源名称 <signerNameDomain>/<signerNamePath>
或匹配 <signerNameDomain>/*
等模式来配置鉴权。spec.signerName
字段派生的前缀命名。
斜杠(/
)被替换为英文冒号(:
),最后追加一个英文冒号,后跟任意名称。
例如,签名者 example.com/mysigner
可以关联到 ClusterTrustBundle example.com:mysigner:<arbitrary-name>
。与签名者关联的 ClusterTrustBundle 通常通过组合签名者名称有关的 字段选择算符 或单独使用标签选择算符在工作负载中被消耗。
签名者未关联的 ClusterTrustBundle 具有空白的 spec.signerName
字段,例如:
apiVersion: certificates.k8s.io/v1alpha1
kind: ClusterTrustBundle
metadata:
name: foo
spec:
# 未指定 signerName 所以该字段留空
trustBundle: "<... PEM data ...>"
它们主要用于集群配置场景。每个与签名者未关联的 ClusterTrustBundle 都是一个独立的对象, 与签名者关联的 ClusterTrustBundle 的惯常分组行为形成了对比。
与签名者为关联的 ClusterTrustBundle 没有 attest
动词要求。
相反,你可以使用通常的机制(如基于角色的访问控制)直接控制对它们的访问。
为了将它们与与签名者关联的 ClusterTrustBundle 区分开来,与签名者未关联的
ClusterTrustBundle 的名称必须不包含英文冒号(:
)。
Kubernetes v1.29 [alpha]
ClusterTrustBundle 的内容可以注入到容器文件系统,这与 ConfigMap 和 Secret 类似。 更多细节参阅 ClusterTrustBundle 投射卷源。
为了让普通用户能够通过认证并调用 API,需要执行几个步骤。 首先,该用户必须拥有 Kubernetes 集群签发的证书, 然后将该证书提供给 Kubernetes API。
下面的脚本展示了如何生成 PKI 私钥和 CSR。 设置 CSR 的 CN 和 O 属性很重要。CN 是用户名,O 是该用户归属的组。 你可以参考 RBAC 了解标准组的信息。
openssl genrsa -out myuser.key 2048
openssl req -new -key myuser.key -out myuser.csr -subj "/CN=myuser"
创建一个 CertificateSigningRequest, 并通过 kubectl 将其提交到 Kubernetes 集群。 下面是生成 CertificateSigningRequest 的脚本。
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: myuser
spec:
request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZqQ0NBVDRDQVFBd0VURVBNQTBHQTFVRUF3d0dZVzVuWld4aE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRgpBQU9DQVE4QU1JSUJDZ0tDQVFFQTByczhJTHRHdTYxakx2dHhWTTJSVlRWMDNHWlJTWWw0dWluVWo4RElaWjBOCnR2MUZtRVFSd3VoaUZsOFEzcWl0Qm0wMUFSMkNJVXBGd2ZzSjZ4MXF3ckJzVkhZbGlBNVhwRVpZM3ExcGswSDQKM3Z3aGJlK1o2MVNrVHF5SVBYUUwrTWM5T1Nsbm0xb0R2N0NtSkZNMUlMRVI3QTVGZnZKOEdFRjJ6dHBoaUlFMwpub1dtdHNZb3JuT2wzc2lHQ2ZGZzR4Zmd4eW8ybmlneFNVekl1bXNnVm9PM2ttT0x1RVF6cXpkakJ3TFJXbWlECklmMXBMWnoyalVnald4UkhCM1gyWnVVV1d1T09PZnpXM01LaE8ybHEvZi9DdS8wYk83c0x0MCt3U2ZMSU91TFcKcW90blZtRmxMMytqTy82WDNDKzBERHk5aUtwbXJjVDBnWGZLemE1dHJRSURBUUFCb0FBd0RRWUpLb1pJaHZjTgpBUUVMQlFBRGdnRUJBR05WdmVIOGR4ZzNvK21VeVRkbmFjVmQ1N24zSkExdnZEU1JWREkyQTZ1eXN3ZFp1L1BVCkkwZXpZWFV0RVNnSk1IRmQycVVNMjNuNVJsSXJ3R0xuUXFISUh5VStWWHhsdnZsRnpNOVpEWllSTmU3QlJvYXgKQVlEdUI5STZXT3FYbkFvczFqRmxNUG5NbFpqdU5kSGxpT1BjTU1oNndLaTZzZFhpVStHYTJ2RUVLY01jSVUyRgpvU2djUWdMYTk0aEpacGk3ZnNMdm1OQUxoT045UHdNMGM1dVJVejV4T0dGMUtCbWRSeEgvbUNOS2JKYjFRQm1HCkkwYitEUEdaTktXTU0xMzhIQXdoV0tkNjVoVHdYOWl4V3ZHMkh4TG1WQzg0L1BHT0tWQW9FNkpsYWFHdTlQVmkKdjlOSjVaZlZrcXdCd0hKbzZXdk9xVlA3SVFjZmg3d0drWm89Ci0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo=
signerName: kubernetes.io/kube-apiserver-client
expirationSeconds: 86400 # one day
usages:
- client auth
EOF
需要注意的几点:
usage
字段必须是 'client auth
'
expirationSeconds
可以设置为更长(例如 864000
是十天)或者更短(例如 3600
是一个小时)
request
字段是 CSR 文件内容的 base64 编码值,
要得到该值,可以执行命令:
cat myuser.csr | base64 | tr -d "\n"
使用 kubectl 创建 CSR 并批准。
获取 CSR 列表:
kubectl get csr
批准 CSR:
kubectl certificate approve myuser
从 CSR 取得证书:
kubectl get csr/myuser -o yaml
证书的内容使用 base64 编码,存放在字段 status.certificate
。
从 CertificateSigningRequest 导出颁发的证书:
kubectl get csr myuser -o jsonpath='{.status.certificate}'| base64 -d > myuser.crt
创建了证书之后,为了让这个用户能访问 Kubernetes 集群资源,现在就要创建 Role 和 RoleBinding 了。
下面是为这个新用户创建 Role 的示例命令:
kubectl create role developer --verb=create --verb=get --verb=list --verb=update --verb=delete --resource=pods
下面是为这个新用户创建 RoleBinding 的示例命令:
kubectl create rolebinding developer-binding-myuser --role=developer --user=myuser
最后一步是将这个用户添加到 kubeconfig 文件。
首先,你需要添加新的凭据:
kubectl config set-credentials myuser --client-key=myuser.key --client-certificate=myuser.crt --embed-certs=true
然后,你需要添加上下文:
kubectl config set-context myuser --cluster=kubernetes --user=myuser
来测试一下,把上下文切换为 myuser
:
kubectl config use-context myuser
下面的表格列举了 PodSecurityPolicy
对象上的配置参数,这些字段是否会变更或检查 Pod 配置,以及这些配置值如何映射到
Pod 安全性标准(Pod Security Standards)
之上。
对于每个可应用的参数,表格中给出了 Baseline 和 Restricted 配置下可接受的取值。 对这两种配置而言不可接受的取值均归入 Privileged 配置下。“无意见”意味着对所有 Pod 安全性标准而言所有取值都可接受。
如果想要了解如何一步步完成迁移,可参阅从 PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器。
下面表格中所列举的字段是 PodSecurityPolicySpec
的一部分,是通过 .spec
字段路径来设置的。
PodSecurityPolicySpec | 类型 | Pod 安全性标准中对应设置 |
---|---|---|
privileged | 检查性质 | Baseline & Restricted: false / 未定义 / nil |
defaultAddCapabilities | 更改性质 & 检查性质 | 需求满足下面的 allallowedCapabilities |
allowedCapabilities | 检查性质 | Baseline:下面各项的子集
Restricted:空 / 未定义 / nil 或仅包含 |
requiredDropCapabilities | 更改性质 & 检查性质 | Baseline:无意见 Restricted:必须包含 |
volumes | 检查性质 | Baseline除下列取值之外的任何值
Restricted:下列取值的子集
|
hostNetwork | 检查性质 | Baseline & Restricted:false / 未定义 / nil |
hostPorts | 检查性质 | Baseline & Restricted:未定义 / nil / 空 |
hostPID | 检查性质 | Baseline & Restricted:false / 未定义 / nil |
hostIPC | 检查性质 | Baseline & Restricted:false / 未定义 / nil |
seLinux | 更改性质 & 检查性质 | Baseline & Restricted:
|
runAsUser | 变更性质 & 检查性质 | Baseline:任何取值 Restricted: |
runAsGroup | 变更性质(MustRunAs)& 检查性质 | 无意见 |
supplementalGroups | 变更性质 & 检查性质 | 无意见 |
fsGroup | 变更性质 & 验证性质 | 无意见 |
readOnlyRootFilesystem | 变更性质 & 检查性质 | 无意见 |
defaultAllowPrivilegeEscalation | 变更性质 | 无意见(非变更性质) |
allowPrivilegeEscalation | 变更性质 & 检查性质 | 只有设置为 Baseline:无意见 Restricted: |
allowedHostPaths | 检查性质 | 无意见(volumes 优先) |
allowedFlexVolumes | 检查性质 | 无意见(volumes 优先) |
allowedCSIDrivers | 检查性质 | 无意见(volumes 优先) |
allowedUnsafeSysctls | 检查性质 | Baseline & Restricted:未定义 / nil / 空 |
forbiddenSysctls | 检查性质 | 无意见 |
allowedProcMountTypes (alpha feature) | 检查性质 | Baseline & Restricted:["Default"] 或者未定义 / nil / 空 |
runtimeClass .defaultRuntimeClassName | 变更性质 | 无意见 |
runtimeClass .allowedRuntimeClassNames | 检查性质 | 无意见 |
下面表格中所列举的注解可以通过
.metadata.annotations
设置到 PodSecurityPolicy 对象之上。
PSP 注解 | 类型 | Pod 安全性标准中对应设置 |
---|---|---|
seccomp.security.alpha.kubernetes.io /defaultProfileName | 变更性质 | 无意见 |
seccomp.security.alpha.kubernetes.io /allowedProfileNames | 检查性质 | Baseline: Restricted:
|
apparmor.security.beta.kubernetes.io /defaultProfileName | 变更性质 | 无意见 |
apparmor.security.beta.kubernetes.io /allowedProfileNames | 检查性质 | Baseline: Restricted:
|
kubelet 的 HTTPS 端点公开了 API, 这些 API 可以访问敏感度不同的数据, 并允许你在节点上和容器内以不同级别的权限执行操作。
本文档介绍了如何对 kubelet 的 HTTPS 端点的访问进行认证和鉴权。
默认情况下,未被已配置的其他身份认证方法拒绝的对 kubelet 的 HTTPS 端点的请求会被视为匿名请求,
并被赋予 system:anonymous
用户名和 system:unauthenticated
组。
要禁用匿名访问并向未经身份认证的请求发送 401 Unauthorized
响应,请执行以下操作:
--anonymous-auth=false
标志启动 kubelet要对 kubelet 的 HTTPS 端点启用 X509 客户端证书认证:
--client-ca-file
标志启动 kubelet,提供一个 CA 证书包以供验证客户端证书--kubelet-client-certificate
和 --kubelet-client-key
标志启动 API 服务器要启用 API 持有者令牌(包括服务帐户令牌)以对 kubelet 的 HTTPS 端点进行身份验证,请执行以下操作:
authentication.k8s.io/v1beta1
API 组--authentication-token-webhook
和 --kubeconfig
标志启动 kubeletTokenReview
API,以根据持有者令牌确定用户信息任何成功通过身份验证的请求(包括匿名请求)之后都会被鉴权。
默认的鉴权模式为 AlwaysAllow
,它允许所有请求。
细分对 kubelet API 的访问权限可能有多种原因:
要细分对 kubelet API 的访问权限,请将鉴权委派给 API 服务器:
authorization.k8s.io/v1beta1
API 组--authorization-mode=Webhook
和 --kubeconfig
标志启动 kubeletSubjectAccessReview
API,
以确定每个请求是否得到鉴权kubelet 使用与 API 服务器相同的 请求属性 方法对 API 请求执行鉴权。
请求的动词根据传入请求的 HTTP 动词确定:
HTTP 动词 | 请求动词 |
---|---|
POST | create |
GET, HEAD | get |
PUT | update |
PATCH | patch |
DELETE | delete |
资源和子资源是根据传入请求的路径确定的:
Kubelet API | 资源 | 子资源 |
---|---|---|
/stats/* | nodes | stats |
/metrics/* | nodes | metrics |
/logs/* | nodes | log |
/spec/* | nodes | spec |
/checkpoint/* | nodes | checkpoint |
其它所有 | nodes | proxy |
名字空间和 API 组属性始终是空字符串,
资源名称始终是 kubelet 的 Node
API 对象的名称。
在此模式下运行时,请确保传递给 API 服务器的由 --kubelet-client-certificate
和
--kubelet-client-key
标志标识的用户具有以下属性的鉴权:
在一个 Kubernetes 集群中,工作节点上的组件(kubelet 和 kube-proxy)需要与 Kubernetes 控制平面组件通信,尤其是 kube-apiserver。 为了确保通信本身是私密的、不被干扰,并且确保集群的每个组件都在与另一个可信的组件通信, 我们强烈建议使用节点上的客户端 TLS 证书。
启动引导这些组件的正常过程,尤其是需要证书来与 kube-apiserver 安全通信的工作节点, 可能会是一个具有挑战性的过程,因为这一过程通常不受 Kubernetes 控制,需要不少额外工作。 这也使得初始化或者扩缩一个集群的操作变得具有挑战性。
为了简化这一过程,从 1.4 版本开始,Kubernetes 引入了一个证书请求和签名 API。 该提案可在这里看到。
本文档描述节点初始化的过程,如何为 kubelet 配置 TLS 客户端证书启动引导, 以及其背后的工作原理。
当工作节点启动时,kubelet 执行以下操作:
kubeconfig
文件kubeconfig
文件中的
TLS 密钥和已签名证书假定 kube-apiserver 成功地认证了 kubelet 的凭据数据,它会将 kubelet 视为一个合法的节点并开始将 Pod 分派给该节点。
注意,签名的过程依赖于:
kubeconfig
中包含密钥和本地主机的证书负责部署和管理集群的人有以下责任:
本文中描述的 TLS 启动引导过程有意简化甚至完全自动化上述过程, 尤其是第三步之后的操作,因为这些步骤是初始化或者扩缩集群时最常见的操作。
在启动引导初始化过程中,会发生以下事情:
kubeconfig
文件bootstrap-kubeconfig
文件kubernetes.io/kube-apiserver-client-kubelet
kubectl
来批复该 CSRkubeconfig
,其中包含密钥和已签名的证书本文的其余部分描述配置 TLS 启动引导的必要步骤及其局限性。
要配置 TLS 启动引导及可选的自动批复,你必须配置以下组件的选项:
ClusterRoleBinding
以及可能需要的 ClusterRole
此外,你需要有 Kubernetes 证书机构(Certificate Authority,CA)。
就像在没有 TLS 启动引导支持的情况下,你会需要证书机构(CA)密钥和证书。 这些数据会被用来对 kubelet 证书进行签名。 如前所述,将证书机构密钥和证书发布到控制平面节点是你的责任。
就本文而言,我们假定这些数据被发布到控制平面节点上的 /var/lib/kubernetes/ca.pem
(证书)和
/var/lib/kubernetes/ca-key.pem
(密钥)文件中。
我们将这两个文件称作“Kubernetes CA 证书和密钥”。
所有 Kubernetes 组件(kubelet、kube-apiserver、kube-controller-manager)
都使用这些凭据,并假定这里的密钥和证书都是 PEM 编码的。
启用 TLS 启动引导对 kube-apiserver 有若干要求:
system:bootstrappers
组对于所有客户端证书的认证操作而言,这是很常见的。
如果还没有设置,要为 kube-apiserver 命令添加 --client-ca-file=FILENAME
标志来启用客户端证书认证,在标志中引用一个包含用来签名的证书的证书机构包,
例如:--client-ca-file=/var/lib/kubernetes/ca.pem
。
为了让启动引导的 kubelet 能够连接到 kube-apiserver 并请求证书, 它必须首先在服务器上认证自身身份。你可以使用任何一种能够对 kubelet 执行身份认证的身份认证组件。
尽管所有身份认证策略都可以用来对 kubelet 的初始启动凭据来执行认证, 但出于容易准备的因素,建议使用如下两个身份认证组件:
启动引导令牌是一种对 kubelet 进行身份认证的方法,相对简单且容易管理, 且不需要在启动 kube-apiserver 时设置额外的标志。
无论选择哪种方法,这里的需求是 kubelet 能够被身份认证为某个具有如下权限的用户:
使用启动引导令牌执行身份认证的 kubelet 会被认证为 system:bootstrappers
组中的用户。这是使用启动引导令牌的一种标准方法。
随着这个功能特性的逐渐成熟,你需要确保令牌绑定到某基于角色的访问控制(RBAC)策略上, 从而严格限制请求(使用启动引导令牌) 仅限于客户端申请提供证书。当 RBAC 被配置启用时,可以将令牌限制到某个组, 从而提高灵活性。例如,你可以在准备节点期间禁止某特定启动引导组的访问。
启动引导令牌的细节在这里详述。 启动引导令牌在 Kubernetes 集群中存储为 Secret 对象,被发放给各个 kubelet。 你可以在整个集群中使用同一个令牌,也可以为每个节点发放单独的令牌。
这一过程有两个方面:
从 kubelet 的角度,所有令牌看起来都很像,没有特别的含义。
从 kube-apiserver 服务器的角度,启动引导令牌是很特殊的。
根据其 type
、namespace
和 name
,kube-apiserver 能够将其认作特殊的令牌,
并授予携带该令牌的任何人特殊的启动引导权限,换言之,将其视为
system:bootstrappers
组的成员。这就满足了 TLS 启动引导的基本需求。
关于创建 Secret 的进一步细节可访问这里。
如果你希望使用启动引导令牌,你必须在 kube-apiserver 上使用下面的标志启用之:
--enable-bootstrap-token-auth=true
kube-apiserver 能够将令牌视作身份认证依据。
这些令牌可以是任意数据,但必须表示为基于某安全的随机数生成器而得到的至少
128 位混沌数据。这里的随机数生成器可以是现代 Linux 系统上的
/dev/urandom
。生成令牌的方式有很多种。例如:
head -c 16 /dev/urandom | od -An -t x | tr -d ' '
上面的命令会生成类似于 02b50b05283e98dd0fd71db496ef01e8
这样的令牌。
令牌文件看起来是下面的例子这样,其中前面三个值可以是任何值, 用引号括起来的组名称则只能用例子中给的值。
02b50b05283e98dd0fd71db496ef01e8,kubelet-bootstrap,10001,"system:bootstrappers"
向 kube-apiserver 添加 --token-auth-file=FILENAME
标志(或许这要对 systemd
的单元文件作修改)以启用令牌文件。有关进一步细节的文档,
可参见这里。
现在启动引导节点被 身份认证 为 system:bootstrappers
组的成员,
它需要被 授权 创建证书签名请求(CSR)并在证书被签名之后将其取回。
幸运的是,Kubernetes 提供了一个 ClusterRole
,其中精确地封装了这些许可,
system:node-bootstrapper
。
为了实现这一点,你只需要创建 ClusterRoleBinding
,将 system:bootstrappers
组绑定到集群角色 system:node-bootstrapper
。
# 允许启动引导节点创建 CSR
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: create-csrs-for-bootstrapping
subjects:
- kind: Group
name: system:bootstrappers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:node-bootstrapper
apiGroup: rbac.authorization.k8s.io
尽管 API 服务器从 kubelet 收到证书请求并对这些请求执行身份认证, 但真正负责发放签名证书的是控制器管理器(controller-manager)。
控制器管理器通过一个证书发放的控制回路来执行此操作。该操作的执行方式是使用磁盘上的文件用 cfssl 本地签名组件来完成。 目前,所发放的所有证书都有一年的有效期,并设定了默认的一组密钥用法。
为了让控制器管理器对证书签名,它需要:
如前所述,你需要创建一个 Kubernetes CA 密钥和证书,并将其发布到控制平面节点。 这些数据会被控制器管理器来对 kubelet 证书进行签名。
由于这些被签名的证书反过来会被 kubelet 用来在 kube-apiserver 执行普通的
kubelet 身份认证,很重要的一点是为控制器管理器所提供的 CA 也被 kube-apiserver
信任用来执行身份认证。CA 密钥和证书是通过 kube-apiserver 的标志
--client-ca-file=FILENAME
(例如 --client-ca-file=/var/lib/kubernetes/ca.pem
)来设定的,
正如 kube-apiserver 配置节所述。
要将 Kubernetes CA 密钥和证书提供给 kube-controller-manager,可使用以下标志:
--cluster-signing-cert-file="/etc/path/to/kubernetes/ca/ca.crt" --cluster-signing-key-file="/etc/path/to/kubernetes/ca/ca.key"
例如:
--cluster-signing-cert-file="/var/lib/kubernetes/ca.pem" --cluster-signing-key-file="/var/lib/kubernetes/ca-key.pem"
所签名的证书的合法期限可以通过下面的标志来配置:
--cluster-signing-duration
为了对 CSR 进行批复,你需要告诉控制器管理器批复这些 CSR 是可接受的。 这是通过将 RBAC 访问权限授予正确的组来实现的。
许可权限有两组:
nodeclient
:如果节点在为节点创建新的证书,则该节点还没有证书。
该节点使用前文所列的令牌之一来执行身份认证,因此是 system:bootstrappers
组的成员。selfnodeclient
:如果节点在对证书执行续期操作,则该节点已经拥有一个证书。
节点持续使用现有的证书将自己认证为 system:nodes
组的成员。要允许 kubelet 请求并接收新的证书,可以创建一个 ClusterRoleBinding
将启动引导节点所处的组 system:bootstrappers
绑定到为其赋予访问权限的 ClusterRole
system:certificates.k8s.io:certificatesigningrequests:nodeclient
:
# 批复 "system:bootstrappers" 组的所有 CSR
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: auto-approve-csrs-for-group
subjects:
- kind: Group
name: system:bootstrappers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:nodeclient
apiGroup: rbac.authorization.k8s.io
要允许 kubelet 对其客户端证书执行续期操作,可以创建一个 ClusterRoleBinding
将正常工作的节点所处的组 system:nodes
绑定到为其授予访问许可的 ClusterRole
system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
:
# 批复 "system:nodes" 组的 CSR 续约请求
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: auto-approve-renewals-for-nodes
subjects:
- kind: Group
name: system:nodes
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
apiGroup: rbac.authorization.k8s.io
作为 kube-controller-manager
的一部分的 csrapproving
控制器是自动被启用的。
该控制器使用 SubjectAccessReview
API
来确定给定用户是否被授权请求 CSR,之后基于鉴权结果执行批复操作。
为了避免与其它批复组件发生冲突,内置的批复组件不会显式地拒绝任何 CSR。
该组件仅是忽略未被授权的请求。控制器也会作为垃圾收集的一部分清除已过期的证书。
最后,当控制平面节点被正确配置并且所有必要的身份认证和鉴权机制都就绪时, 我们可以配置 kubelet。
kubelet 需要以下配置来执行启动引导:
kubeconfig
文件的路径;kubelet 会将启动引导配置文件放到这个位置kubeconfig
文件的路径,用来提供 API 服务器的 URL 和启动引导凭据,
例如,启动引导令牌启动引导 kubeconfig
文件应该放在一个 kubelet 可访问的路径下,例如
/var/lib/kubelet/bootstrap-kubeconfig
。
其格式与普通的 kubeconfig
文件完全相同。示例文件可能看起来像这样:
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: /var/lib/kubernetes/ca.pem
server: https://my.server.example.com:6443
name: bootstrap
contexts:
- context:
cluster: bootstrap
user: kubelet-bootstrap
name: bootstrap
current-context: bootstrap
preferences: {}
users:
- name: kubelet-bootstrap
user:
token: 07401b.f395accd246ae52d
需要额外注意的一些因素有:
certificate-authority
:指向 CA 文件的路径,用来对 kube-apiserver 所出示的服务器证书进行验证server
:用来访问 kube-apiserver 的 URLtoken
:要使用的令牌令牌的格式并不重要,只要它与 kube-apiserver 的期望匹配即可。 在上面的例子中,我们使用的是启动引导令牌。 如前所述,任何合法的身份认证方法都可以使用,不限于令牌。
因为启动引导 kubeconfig
文件是一个标准的 kubeconfig
文件,你可以使用
kubectl
来生成该文件。要生成上面的示例文件:
kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig set-cluster bootstrap --server='https://my.server.example.com:6443' --certificate-authority=/var/lib/kubernetes/ca.pem
kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig set-credentials kubelet-bootstrap --token=07401b.f395accd246ae52d
kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig set-context bootstrap --user=kubelet-bootstrap --cluster=bootstrap
kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig use-context bootstrap
要指示 kubelet 使用启动引导 kubeconfig
文件,可以使用下面的 kubelet 标志:
--bootstrap-kubeconfig="/var/lib/kubelet/bootstrap-kubeconfig" --kubeconfig="/var/lib/kubelet/kubeconfig"
在启动 kubelet 时,如果 --kubeconfig
标志所指定的文件并不存在,会使用通过标志
--bootstrap-kubeconfig
所指定的启动引导 kubeconfig 配置来向 API 服务器请求客户端证书。
在证书请求被批复并被 kubelet 收回时,一个引用所生成的密钥和所获得证书的 kubeconfig
文件会被写入到通过 --kubeconfig
所指定的文件路径下。
证书和密钥文件会被放到 --cert-dir
所指定的目录中。
前文所述的内容都与 kubelet 客户端证书相关,尤其是 kubelet 用来向 kube-apiserver 认证自身身份的证书。
kubelet 也可以使用 服务(Serving) 证书。kubelet 自身向外提供一个 HTTPS 末端, 包含若干功能特性。要保证这些末端的安全性,kubelet 可以执行以下操作之一:
--tls-private-key-file
和 --tls-cert-file
所设置的密钥和证书TLS 启动引导所提供的客户端证书默认被签名为仅用于 client auth
(客户端认证),
因此不能作为提供服务的证书,或者 server auth
。
不过,你可以启用服务器证书,至少可以部分地通过证书轮换来实现这点。
Kubernetes v1.8 和更高版本的 kubelet 实现了对客户端证书与/或服务证书进行轮换这一特性。
请注意,服务证书轮换是一项 Beta 特性,需要 kubelet 上 RotateKubeletServerCertificate
特性的支持(默认启用)。
你可以配置 kubelet 使其在现有凭据过期时通过创建新的 CSR 来轮换其客户端证书。
要启用此功能,请使用 kubelet 配置文件的
rotateCertificates
字段或将以下命令行参数传递给 kubelet(已弃用):
--rotate-certificates
启用 RotateKubeletServerCertificate
会让 kubelet
在启动引导其客户端凭据之后请求一个服务证书且对该服务证书执行轮换操作。
要启用此特性,请使用 kubelet 配置文件的
serverTLSBootstrap
字段将以下命令行参数传递给 kubelet(已弃用):
--rotate-server-certificates
出于安全原因,Kubernetes 核心中所实现的
CSR 批复控制器并不会自动批复节点的服务证书。
要使用 RotateKubeletServerCertificate
功能特性,
集群运维人员需要运行一个定制的控制器或者手动批复服务证书的请求。
对 kubelet 服务证书的批复过程因集群部署而异,通常应该仅批复如下 CSR:
spec.username
字段形式为 system:node:<nodeName>
且 spec.groups
包含 system:nodes
)spec.usages
中包含 server auth
,可选地也可包含
digital signature
和 key encipherment
,且不包含其它用法)subjectAltNames
,没有 URI 和 Email
形式的 subjectAltNames
(解析 spec.request
中的 x509 证书签名请求可以检查
subjectAltNames
)本文所描述的所有 TLS 启动引导内容都与 kubelet 相关。不过,其它组件也可能需要直接与 kube-apiserver 直接通信。容易想到的是 kube-proxy,同样隶属于 Kubernetes 的节点组件并且运行在所有节点之上,不过也可能包含一些其它负责监控或者联网的组件。
与 kubelet 类似,这些其它组件也需要一种向 kube-apiserver 认证身份的方法。 你可以用几种方法来生成这类凭据:
kube-system
名字空间中的
DaemonSet 来执行,而不是独立的进程。由于 DaemonSet 位于集群内部,
你可以为其指派一个合适的服务账户,使之具有适当的访问权限来完成其使命。
这也许是配置此类服务的最简单的方法。CSR 可以在编译进控制器管理器内部的批复工作流之外被批复。
签名控制器并不会立即对所有证书请求执行签名操作。相反,
它会等待这些请求被某具有适当特权的用户标记为 “Approved(已批准)”状态。
这一流程有意允许由外部批复控制器来自动执行的批复,
或者由控制器管理器内置的批复控制器来自动批复。
不过,集群管理员也可以使用 kubectl
来手动批准证书请求。
管理员可以通过 kubectl get csr
来列举所有的 CSR,使用
kubectl descsribe csr <name>
来描述某个 CSR 的细节。
管理员可以使用 kubectl certificate approve <name
来批准某 CSR,或者
kubectl certificate deny <name>
来拒绝某 CSR。
Kubernetes v1.30 [stable]
本页面提供验证准入策略(Validating Admission Policy)的概述。
验证准入策略提供一种声明式的、进程内的替代方案来验证准入 Webhook。
验证准入策略使用通用表达语言 (Common Expression Language,CEL) 来声明策略的验证规则。 验证准入策略是高度可配置的,使配置策略的作者能够根据集群管理员的需要, 定义可以参数化并限定到资源的策略。
策略通常由三种资源构成:
ValidatingAdmissionPolicy
描述策略的抽象逻辑(想想看:“这个策略确保一个特定标签被设置为一个特定值”)。
一个 ValidatingAdmissionPolicyBinding
将上述资源联系在一起,并提供作用域。
如果你只想为 Pods
设置一个 owner
标签,你就需要在这个绑定中指定这个限制。
参数资源为 ValidatingAdmissionPolicy
提供信息,使其成为一个具体的声明
(想想看:“owner
标签必须被设置为以 .company.com
结尾的形式")。
参数资源的模式(Schema)使用诸如 ConfigMap 或 CRD 这类原生类型定义。
ValidatingAdmissionPolicy
对象指定它们期望参数资源所呈现的类型。
至少要定义一个 ValidatingAdmissionPolicy
和一个相对应的 ValidatingAdmissionPolicyBinding
才能使策略生效。
如果 ValidatingAdmissionPolicy
不需要参数配置,不设置 ValidatingAdmissionPolicy
中的
spec.paramKind
即可。
验证准入策略是集群控制平面的一部分。你应该非常谨慎地编写和部署它们。下面介绍如何快速试验验证准入策略。
以下是一个 ValidatingAdmissionPolicy 的示例:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
spec.validations
包含使用通用表达式语言 (CEL)
来验证请求的 CEL 表达式。
如果表达式的计算结果为 false,则根据 spec.failurePolicy
字段强制执行验证检查处理。
你可以在 CEL Playground 中快速验证 CEL 表达式。
要配置一个在某集群中使用的验证准入策略,需要一个绑定。 以下是一个 ValidatingAdmissionPolicyBinding 的示例:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "demo-binding-test.example.com"
spec:
policyName: "demo-policy.example.com"
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
environment: test
尝试创建副本集合不满足验证表达式的 Deployment 时,将返回包含以下消息的错误:
ValidatingAdmissionPolicy 'demo-policy.example.com' with binding 'demo-binding-test.example.com' denied request: failed expression: object.spec.replicas <= 5
上面提供的是一个简单的、无配置参数的 ValidatingAdmissionPolicy。
每个 ValidatingAdmissionPolicyBinding
必须指定一个或多个
validationActions
来声明如何执行策略的 validations
。
支持的 validationActions
包括:
Deny
:验证失败会导致请求被拒绝。Warn
:验证失败会作为警告报告给请求客户端。Audit
:验证失败会包含在 API 请求的审计事件中。例如,要同时向客户端发出验证失败的警告并记录验证失败的审计记录,请使用以下配置:
validationActions: [Warn, Audit]
Deny
和 Warn
不能一起使用,因为这种组合会不必要地将验证失败重复输出到
API 响应体和 HTTP 警告头中。
如果 validation
求值为 false,则始终根据这些操作执行。
由 failurePolicy
定义的失败仅在 failurePolicy
设置为 Fail
(或未指定)时根据这些操作执行,
否则这些失败将被忽略。
有关验证失败审计注解的详细信息, 请参见审计注解:验证失败。
参数资源允许策略配置与其定义分开。 一个策略可以定义 paramKind,给出参数资源的 GVK, 然后一个策略绑定可以通过名称(通过 policyName)将某策略与某特定的参数资源(通过 paramRef)联系起来。
如果需要参数配置,下面是一个带有参数配置的 ValidatingAdmissionPolicy 的例子:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "replicalimit-policy.example.com"
spec:
failurePolicy: Fail
paramKind:
apiVersion: rules.example.com/v1
kind: ReplicaLimit
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= params.maxReplicas"
reason: Invalid
ValidatingAdmissionPolicy 的 spec.paramKind
字段指定用于参数化此策略的资源类型。
在这个例子中,它是由自定义资源 ReplicaLimit 配置的。
在这个例子中请注意 CEL 表达式是如何通过 CEL params 变量引用参数的,如:params.maxReplicas
。
spec.matchConstraints
指定此策略要检查哪些资源。
请注意,诸如 ConfigMap
之类的原生类型也可以用作参数引用。
spec.validations
字段包含 CEL 表达式。
如果表达式的计算结果为 false,则根据 spec.failurePolicy
字段强制执行验证检查操作。
验证准入策略的作者负责提供 ReplicaLimit 参数 CRD。
要配置一个在某集群中使用的验证准入策略,需要创建绑定和参数资源。 以下是 ValidatingAdmissionPolicyBinding 集群范围参数的示例 - 相同的参数将用于验证与绑定匹配的每个资源请求:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "replicalimit-binding-test.example.com"
spec:
policyName: "replicalimit-policy.example.com"
validationActions: [Deny]
paramRef:
name: "replica-limit-test.example.com"
namespace: "default"
matchResources:
namespaceSelector:
matchLabels:
environment: test
请注意,此绑定将参数应用于 test
环境中所有资源的策略中。
参数资源可以如下:
apiVersion: rules.example.com/v1
kind: ReplicaLimit
metadata:
name: "replica-limit-test.example.com"
namespace: "default"
maxReplicas: 3
此策略参数资源将限制 Deployment 最多有 3 个副本。 一个准入策略可以有多个绑定。 要绑定所有的其他环境,限制 maxReplicas 为 100,请创建另一个 ValidatingAdmissionPolicyBinding:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "replicalimit-binding-nontest"
spec:
policyName: "replicalimit-policy.example.com"
validationActions: [Deny]
paramRef:
name: "replica-limit-prod.example.com"
namespace: "default"
matchResources:
namespaceSelector:
matchExpressions:
- key: environment
operator: NotIn
values:
- test
请注意,此绑定将不同的参数应用于不在 test
环境中的资源。
并有一个参数资源:
apiVersion: rules.example.com/v1
kind: ReplicaLimit
metadata:
name: "replica-limit-prod.example.com"
maxReplicas: 100
对于每个准入请求,API 服务器都会评估与请求匹配的每个(策略、绑定、参数)组合的 CEL 表达式。 要获得准入资格,必须通过所有评估。
如果多个绑定与请求匹配,则将为每个绑定评估策略,并且它们必须全部通过评估,策略才会被视为通过。
如果多个参数与同一个绑定匹配,则系统将为每个参数评估策略规则,并且这些规则也必须全部通过才能认为该绑定通过。 多个绑定之间可以在匹配条件存在重叠。系统针对匹配的绑定参数所有组合来评估策略。如果多个绑定与其匹配, 或者同一个绑定与多个参数匹配,则策略甚至可以被多次评估。
如果参数资源尚未被绑定,代表参数资源的 params 对象将不会被设置,
所以对于需要参数资源的策略,添加一个检查来确保参数资源被绑定,这会很有用。
如果策略的 paramKind
未指定或绑定的 paramRef
未指定,则不会绑定参数资源,
并且 params
将为空。
对于需要参数配置的场景,我们建议在 spec.validations[0].expression
中添加一个参数检查:
- expression: "params != null"
message: "params missing but required to bind to this policy"
将可选参数作为参数资源的一部分,并且只在参数存在时执行检查操作,这样做会比较方便。
CEL 提供了 has()
方法,它检查传递给它的键是否存在。CEL 还实现了布尔短路逻辑。
如果逻辑 OR 的前半部分计算为 true,则不会计算另一半(因为无论如何整个 OR 的结果都为真)。
结合这两者,我们可以提供一种验证可选参数的方法:
!has(params.optionalNumber) || (params.optionalNumber >= 5 && params.optionalNumber <= 10)
在这里,我们首先用 !has(params.optionalNumber)
检查可选参数是否存在。
optionalNumber
没有被定义,那么表达式就会短路,因为 !has(params.optionalNumber)
的计算结果为 true。optionalNumber
被定义了,那么将计算 CEL 表达式的后半部分,
并且 optionalNumber
将被检查以确保它包含一个 5 到 10 之间的值(含 5 到 10)。作为 ValidatingAdmissionPolicy 及其 ValidatingAdmissionPolicyBinding 的作者,
你可以选择指定其作用于集群范围还是某个命名空间。如果你为绑定的 paramRef
指定 namespace
,
则控制平面仅在该命名空间中搜索参数。
但是,如果 ValidatingAdmissionPolicyBinding 中未指定 namespace
,则 API
服务器可以在请求所针对的命名空间中搜索相关参数。
例如,如果你请求修改 default
命名空间中的 ConfigMap,并且存在未设置 namespace
的相关
ValidatingAdmissionPolicyBinding,则 API 服务器在 default
命名空间中查找参数对象。
此设计支持依赖于所操作资源的命名空间的策略配置,以实现更精细的控制。
除了在绑定中用 name
来指定参数之外,你还可以选择设置标签选择算符,
这样对于与策略的 paramKind
参数匹配,且位于参数的 namespace
(如果适用)内的所有资源,
如果与标签选择算符匹配,都会被评估。有关标签选择算符如何匹配资源的更多信息,
请参阅选择算符。
如果发现多个参数满足条件,则会针对所找到的每个参数来评估策略规则,并将结果进行“与”运算。
如果设置了 namespace
,则只有所提供的命名空间中类别为 paramKind
的对象才会被匹配。
否则,当 namespace
为空且 paramKind
为命名空间作用域的资源时,使用被准入请求中指定的 namespace
。
我们为参数资源引入了鉴权检查。
用户应该对 ValidatingAdmissionPolicy
中的 paramKind
和 ValidatingAdmissionPolicyBinding
中的 paramRef
所引用的资源有 read
权限。
请注意,如果 paramKind
中的资源没能通过 restmapper 解析,则用户需要拥有对组的所有资源的
read
访问权限。
failurePolicy
定义了如何处理错误配置和准入策略的 CEL 表达式取值为 error 的情况。
允许的值是 Ignore
或 Fail
。
Ignore
意味着调用 ValidatingAdmissionPolicy 的错误被忽略,允许 API 请求继续。Fail
意味着调用 ValidatingAdmissionPolicy 的错误导致准入失败并拒绝 API 请求。请注意,failurePolicy
是在 ValidatingAdmissionPolicy
中定义的:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
spec:
...
failurePolicy: Ignore # 默认为 "Fail"
validations:
- expression: "object.spec.xyz == params.x"
spec.validations[i].expression
代表将使用 CEL 来计算表达式。
要了解更多信息,请参阅 CEL 语言规范。
CEL 表达式可以访问按 CEL 变量来组织的 Admission 请求/响应的内容,以及其他一些有用的变量 :
paramKind
,该值为 null。namespaceObject
- 作为 Kubernetes 资源的、传输对象所在的名字空间。
如果传入对象是集群作用域的,则此值为 null。authorizer
- 一个 CEL 鉴权组件。可以用来为请求的主体(经过身份验证的用户)执行鉴权检查。
更多细节可以参考 AuthzSelectors
和 Kubernetes CEL 库的文档中的 Authz。authorizer.requestResource
- 针对请求资源(组、资源、(子资源)、命名空间、名称)所配置的鉴权检查的快捷方式。总是可以从对象的根访问的属性有 apiVersion
、kind
、metadata.name
和 metadata.generateName
。
其他元数据属性不能访问。
列表类型为 "set" 或 "map" 的数组上的等价关系比较会忽略元素顺序,即 [1, 2] == [2, 1]。 使用 x-kubernetes-list-type 连接数组时使用列表类型的语义:
X + Y
执行并集,其中 X
中所有元素的数组位置被保留,Y
中不相交的元素被追加,保留其元素的偏序关系。X + Y
执行合并,保留 X
中所有键的数组位置,但是当 X
和 Y
的键集相交时,其值被 Y
的值覆盖。
Y
中键值不相交的元素被追加,保留其元素之间的偏序关系。表达式 | 目的 |
---|---|
object.minReplicas <= object.replicas && object.replicas <= object.maxReplicas | 检查定义副本的三个字段是否大小关系正确 |
'Available' in object.stateCounts | 检查映射中是否存在键为 Available 的条目 |
(size(object.list1) == 0) != (size(object.list2) == 0) | 检查两个列表是否有且只有一个非空 |
!('MY_KEY' in object.map1) || object['MY_KEY'].matches('^[a-zA-Z]*$') | 检查映射中存在特定的键时其取值符合某规则 |
object.envars.filter(e, e.name == 'MY_ENV').all(e, e.value.matches('^[a-zA-Z]*$') | 验证 listMap 中所有键名为 "MY_ENV" 的条目的 “value” 字段,确保其符合规则 |
has(object.expired) && object.created + object.ttl < object.expired | 检查 expired 日期在 create 日期加上 ttl 时长之后 |
object.health.startsWith('ok') | 检查 health 字符串字段的取值有 “ok” 前缀 |
object.widgets.exists(w, w.key == 'x' && w.foo < 10) | 对于 listMap 中键为 “x” 的条目,检查该条目的 "foo" 属性的值是否小于 10 |
type(object) == string ? object == '100%' : object == 1000 | 对于 int-or-string 字段,分别处理类型为 int 或 string 的情况 |
object.metadata.name.startsWith(object.prefix) | 检查对象名称是否以另一个字段值为前缀 |
object.set1.all(e, !(e in object.set2)) | 检查两个 listSet 是否不相交 |
size(object.names) == size(object.details) && object.names.all(n, n in object.details) | 检查映射 “details” 所有的键和 listSet “names” 中的条目是否一致 |
size(object.clusters.filter(c, c.name == object.primary)) == 1 | 检查 “primary” 字段的值在 listMap “clusters” 中只出现一次 |
了解关于 CEL 规则的更多信息, 请阅读 CEL 支持的求值表达式。
spec.validation[i].reason
表示一个机器可读的描述,说明为什么这次检查失败。
如果这是列表中第一个失败的检查,其原因以及相应的 HTTP 响应代码会被用在给客户端的 HTTP 响应中。
目前支持的原因有:Unauthorized
、Forbidden
、Invalid
、RequestEntityTooLarge
。
如果未设置,将在对客户端的响应中使用 StatusReasonInvalid
。
matchConditions
如果需要进行精细的请求过滤,可以为 ValidatingAdmissionPolicy
定义 匹配条件(match conditions)。
如果发现匹配规则、objectSelectors
和 namespaceSelectors
仍无法提供所需的过滤功能,则使用这些条件非常有用。
匹配条件是 CEL 表达式。
所有匹配条件必须求值为 true 时才会对资源进行评估。
以下示例说明了匹配条件的几个不同用法:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["*"]
apiVersions: ["*"]
operations: ["CREATE", "UPDATE"]
resources: ["*"]
matchConditions:
- name: 'exclude-leases' # 每个匹配条件必须有一个唯一的名称
expression: '!(request.resource.group == "coordination.k8s.io" && request.resource.resource == "leases")' # 匹配非租约资源
- name: 'exclude-kubelet-requests'
expression: '!("system:nodes" in request.userInfo.groups)' # 匹配非节点用户发出的请求
- name: 'rbac' # 跳过 RBAC 请求
expression: 'request.resource.group != "rbac.authorization.k8s.io"'
validations:
- expression: "!object.metadata.name.contains('demo') || object.metadata.namespace == 'demo'"
这些匹配条件可以访问与验证表达式相同的 CEL 变量。
在评估匹配条件时出现错误时,将不会评估策略。根据以下方式确定是否拒绝请求:
如果任何一个匹配条件求值结果为 false
(不管其他错误),API 服务器将跳过 Webhook。
否则:
failurePolicy: Fail
,拒绝请求(不调用 Webhook)。failurePolicy: Ignore
,继续处理请求但跳过 Webhook。auditAnnotations
可用于在 API 请求的审计事件中包括审计注解。
例如,以下是带有审计注解的准入策略:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas > 50"
messageExpression: "'Deployment spec.replicas set to ' + string(object.spec.replicas)"
auditAnnotations:
- key: "high-replica-count"
valueExpression: "'Deployment spec.replicas set to ' + string(object.spec.replicas)"
当使用此准入策略验证 API 请求时,生成的审计事件将如下所示:
# 记录的审计事件
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"annotations": {
"demo-policy.example.com/high-replica-count": "Deployment spec.replicas set to 128"
# 其他注解
...
}
# 其他字段
...
}
在此示例中,只有 Deployment 的 spec.replicas
大于 50 时才会包含注解,
否则 CEL 表达式将求值为 null,并且不会包含注解。
请注意,审计注解键以 ValidatingAdmissionWebhook
的名称和 /
为前缀。
如果另一个准入控制器(例如准入 Webhook)使用完全相同的审计注解键,
则第一个包括审计注解值的准入控制器将出现在审计事件中,而所有其他值都将被忽略。
为了在策略拒绝请求时返回更友好的消息,我们在 spec.validations[i].messageExpression
中使用 CEL 表达式来构造消息。
与验证表达式类似,消息表达式可以访问 object
、oldObject
、request
和 params
。
但是,与验证不同,消息表达式必须求值为字符串。
例如,为了在策略引用参数时更好地告知用户拒绝原因,我们可以有以下验证:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "deploy-replica-policy.example.com"
spec:
paramKind:
apiVersion: rules.example.com/v1
kind: ReplicaLimit
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= params.maxReplicas"
messageExpression: "'object.spec.replicas must be no greater than ' + string(params.maxReplicas)"
reason: Invalid
在创建限制副本为 3 的 Params 对象并设置绑定之后,当我们尝试创建具有 5 个副本的 Deployment 时,我们将收到以下消息:
$ kubectl create deploy --image=nginx nginx --replicas=5
error: failed to create deployment: deployments.apps "nginx" is forbidden: ValidatingAdmissionPolicy 'deploy-replica-policy.example.com' with binding 'demo-binding-test.example.com' denied request: object.spec.replicas must be no greater than 3
这比静态消息 "too many replicas" 更具说明性。
如果既定义了消息表达式,又在 spec.validations[i].message
中定义了静态消息,
则消息表达式优先于静态消息。但是,如果消息表达式求值失败,则将使用静态消息。
此外,如果消息表达式求值为多行字符串,则会丢弃求值结果并使用静态消息(如果存在)。
请注意,静态消息也要检查是否存在多行字符串。
创建或更新策略定义时,验证过程将解析它包含的表达式,在发现错误时报告语法错误并拒绝该定义。
之后,引用的变量将根据 spec.matchConstraints
的匹配类型检查类型错误,包括缺少字段和类型混淆。
类型检查的结果可以从 status.typeChecking
中获得。
status.typeChecking
的存在表示类型检查已完成,而空的 status.typeChecking
表示未发现错误。
例如,给定以下策略定义:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "deploy-replica-policy.example.com"
spec:
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.replicas > 1" # 应为 "object.spec.replicas > 1"
message: "must be replicated"
reason: Invalid
status 字段将提供以下信息:
status:
typeChecking:
expressionWarnings:
- fieldRef: spec.validations[0].expression
warning: |-
apps/v1, Kind=Deployment: ERROR: <input>:1:7: undefined field 'replicas'
| object.replicas > 1
| ......^
如果在 spec.matchConstraints
中匹配了多个资源,则所有匹配的资源都将进行检查。
例如,以下策略定义:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "replica-policy.example.com"
spec:
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments","replicasets"]
validations:
- expression: "object.replicas > 1" # 应为 "object.spec.replicas > 1"
message: "must be replicated"
reason: Invalid
将具有多个类型,并在警告消息中提供每种类型的类型检查结果。
status:
typeChecking:
expressionWarnings:
- fieldRef: spec.validations[0].expression
warning: |-
apps/v1, Kind=Deployment: ERROR: <input>:1:7: undefined field 'replicas'
| object.replicas > 1
| ......^
apps/v1, Kind=ReplicaSet: ERROR: <input>:1:7: undefined field 'replicas'
| object.replicas > 1
| ......^
类型检查具有以下限制:
spec.matchConstraints.resourceRules
中的任何一个 apiGroups
、apiVersions
或 resources
包含 "\*"
,则不会检查与 "\*"
匹配的类型。如果表达式变得太复杂,或者表达式的一部分可重用且进行评估时计算开销较大,可以将表达式的某些部分提取为变量。
变量是一个命名表达式,后期可以在其他表达式中的 variables
中引用。
spec:
variables:
- name: foo
expression: "'foo' in object.spec.metadata.labels ? object.spec.metadata.labels['foo'] : 'default'"
validations:
- expression: variables.foo == 'bar'
变量在首次引用时会被延迟求值。评估期间发生的任何错误都将在评估引用表达式期间报告, 结果和可能的错误都会被记录下来,且在运行时开销中仅计为一次。
变量的顺序很重要,因为一个变量可以引用在它之前定义的其他变量。 对顺序的要求可以防止循环引用。
以下是强制镜像仓库名称与其命名空间中定义的环境相匹配的一个较复杂示例。
# 此策略强制除 "exempt" Deployment 或任何不属于 “example.com” 组织的容器
#(例如常见的 sidecar)外的 Deployment 的所有容器的镜像库与其命名空间的环境标签相匹配。
# 例如,如果命名空间的标签为 {"environment": "staging"},则所有容器镜像必须是
# staging.example.com/* 或根本不包含 “example.com”,除非 Deployment 有
# {"exempt": "true"} 标签。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "image-matches-namespace-environment.policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
variables:
- name: environment
expression: "'environment' in namespaceObject.metadata.labels ? namespaceObject.metadata.labels['environment'] : 'prod'"
- name: exempt
expression: "'exempt' in object.metadata.labels && object.metadata.labels['exempt'] == 'true'"
- name: containers
expression: "object.spec.template.spec.containers"
- name: containersToCheck
expression: "variables.containers.filter(c, c.image.contains('example.com/'))"
validations:
- expression: "variables.exempt || variables.containersToCheck.all(c, c.image.startsWith(variables.environment + '.'))"
messageExpression: "'only ' + variables.environment + ' images are allowed in namespace ' + namespaceObject.metadata.name"
在此策略被绑定到 default
命名空间(标签为 environment: prod
)的情况下,
以下创建 Deployment 的尝试将被拒绝。
kubectl create deploy --image=dev.example.com/nginx invalid
错误信息类似于:
error: failed to create deployment: deployments.apps "invalid" is forbidden: ValidatingAdmissionPolicy 'image-matches-namespace-environment.policy.example.com' with binding 'demo-binding-test.example.com' denied request: only prod images are allowed in namespace default