用户模拟 (User Impersonation) 是一种允许已认证用户通过 HTTP 头信息以其他用户、组或服务账号身份行事的方法。
用户可以通过模拟头信息以其他用户身份行事。这些头信息允许请求手动覆盖请求所认证的用户信息。例如,管理员可以使用此功能通过临时模拟其他用户来调试授权策略,并观察请求是否被拒绝。
模拟请求首先会以请求者的身份进行认证,然后切换到被模拟的用户信息。
以下 HTTP 头信息可用于执行模拟请求:
Impersonate-User: 要模拟的用户名。Impersonate-Uid: 代表被模拟用户的唯一标识符。可选。需要 "Impersonate-User"。Kubernetes 对此字符串的格式没有任何要求。Impersonate-Group: 要模拟的组名。可以多次提供以设置多个组。可选。需要 "Impersonate-User"。Impersonate-Extra-( extra name ): 用于将额外字段与用户关联的动态头信息。可选。需要 "Impersonate-User"。为了保持一致,( extra name ) 必须是小写,并且任何在 HTTP 头标签中不合法的字符都必须是 utf8 并经过百分号编码。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-Uid: 06f6ce97-e2c5-4ab8-7ba5-7654dd08d52b
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
当使用 kubectl 时,设置 --as 命令行参数以配置 Impersonate-User 头,你还可以设置 --as-group 标志来配置 Impersonate-Group 头,设置 --as-uid 标志(1.23 版本引入)来配置 Impersonate-Uid 头,以及设置 --as-user-extra 标志(1.35 版本引入)来配置 Impersonate-Extra-( extra name ) 头。
kubectl drain mynode
Error from server (Forbidden): User "clark" cannot get nodes at the cluster scope. (get nodes mynode)
设置 --as 和 --as-group 标志
kubectl drain mynode --as=superman --as-group=system:masters
node/mynode cordoned
node/mynode drained
要模拟用户、用户标识符 (UID)、组或额外字段,模拟者必须有能力在被模拟属性的类型("user"、"uid"、"group" 等)上执行 impersonate 动词。对于启用了 RBAC 授权插件的集群,以下 ClusterRole 包含了设置用户和组模拟头信息所需的规则:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: impersonator
rules:
- apiGroups: [""]
resources: ["users", "groups", "serviceaccounts"]
verbs: ["impersonate"]
对于模拟,额外字段和被模拟的 UID 都位于 "authentication.k8s.io" apiGroup 下。额外字段被评估为资源 "userextras" 的子资源。要允许用户将模拟头信息用于额外字段 scopes 和 UID,应授予用户以下角色:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: scopes-and-uid-impersonator
rules:
# Can set "Impersonate-Extra-scopes" header and the "Impersonate-Uid" header.
- apiGroups: ["authentication.k8s.io"]
resources: ["userextras/scopes", "uids"]
verbs: ["impersonate"]
模拟头信息的值也可以通过限制资源可以采用的 resourceNames 集合来加以限制。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: limited-impersonator
rules:
# Can impersonate the user "jane.doe@example.com"
- apiGroups: [""]
resources: ["users"]
verbs: ["impersonate"]
resourceNames: ["jane.doe@example.com"]
# Can impersonate the groups "developers" and "admins"
- apiGroups: [""]
resources: ["groups"]
verbs: ["impersonate"]
resourceNames: ["developers","admins"]
# Can impersonate the extras field "scopes" with the values "view" and "development"
- apiGroups: ["authentication.k8s.io"]
resources: ["userextras/scopes"]
verbs: ["impersonate"]
resourceNames: ["view", "development"]
# Can impersonate the uid "06f6ce97-e2c5-4ab8-7ba5-7654dd08d52b"
- apiGroups: ["authentication.k8s.io"]
resources: ["uids"]
verbs: ["impersonate"]
resourceNames: ["06f6ce97-e2c5-4ab8-7ba5-7654dd08d52b"]
模拟用户或组允许你执行任何操作,就像你是那个用户或组一样;因此,模拟不是命名空间作用域的。如果你想使用 Kubernetes RBAC 允许模拟,这需要使用 ClusterRole 和 ClusterRoleBinding,而不是 Role 和 RoleBinding。
授予对 ServiceAccount 的模拟权限是命名空间作用域的,但被模拟的 ServiceAccount 可能在命名空间之外执行操作。
Kubernetes v1.36 [beta](默认启用)使用 impersonate 动词时,模拟无法被限制或限定范围。它要么授予完全模拟权限,要么完全不授予。一旦被授予模拟用户的权限,你就可以执行该用户在所有资源和命名空间中能执行的任何操作。
通过受限模拟,可以将模拟者限制为仅在特定资源上针对特定操作模拟另一个用户,而不是能够执行被模拟用户能执行的所有操作。
此特性通过设置 ConstrainedImpersonation 特性门控来启用。
受限模拟需要两个独立的权限:
default 命名空间中 list 和 watch Pod)这意味着模拟者可以被限制为仅针对特定操作模拟另一个用户。
受限模拟定义了三种不同的模式,每种模式都有其自己的一套动词:
使用此模式模拟普通用户(非服务账号或节点)。此模式适用于 Impersonate-User 头值:
system:serviceaccount: 开头system:node: 开头动词:
impersonate:user-info - 模拟特定用户、组、UID 或额外字段的权限impersonate-on:user-info:<verb> - 模拟普通用户时执行 <verb> 的权限使用此模式模拟 ServiceAccount。
动词:
impersonate:serviceaccount - 模拟特定服务账号的权限impersonate-on:serviceaccount:<verb> - 模拟服务账号时执行 <verb> 的权限使用这些模式来模拟节点。此模式适用于 Impersonate-User 头值以 system:node: 开头的情况。
动词:
impersonate:arbitrary-node - 模拟任何指定节点的权限impersonate:associated-node - 仅模拟与模拟者绑定的节点的权限impersonate-on:arbitrary-node:<verb> - 模拟任何节点时执行 <verb> 的权限impersonate-on:associated-node:<verb> - 模拟关联节点时执行 <verb> 的权限impersonate:associated-node 动词仅在模拟者是绑定到其试图模拟的节点的服务账号时适用。这通过检查服务账号的用户信息是否包含键为 authentication.kubernetes.io/node-name 且匹配被模拟节点的额外字段来确定。所有受限模拟权限都使用 authentication.k8s.io API 组。以下是如何配置不同模式的方法:
此示例展示了如何允许服务账号模拟名为 jane.doe@example.com 的用户,但仅限于在 default 命名空间中 list 和 watch Pod。你需要同时配置用于身份权限的 ClusterRoleBinding 和用于操作权限的 RoleBinding。
第一步:授予模拟用户身份的权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: impersonate-jane-identity
rules:
- apiGroups: ["authentication.k8s.io"]
resources: ["users"]
resourceNames: ["jane.doe@example.com"]
verbs: ["impersonate:user-info"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: impersonate-jane-identity
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: impersonate-jane-identity
subjects:
- kind: ServiceAccount
name: my-controller
namespace: default
第二步:授予模拟时执行特定操作的权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: impersonate-list-watch-pods
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs:
- "impersonate-on:user-info:list"
- "impersonate-on:user-info:watch"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: impersonate-list-watch-pods
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: impersonate-list-watch-pods
subjects:
- kind: ServiceAccount
name: my-controller
namespace: default
现在 my-controller 服务账号可以模拟 jane.doe@example.com 以列出和观察 default 命名空间中的 Pod,但不能执行诸如删除 Pod 或访问其他命名空间中的资源等其他操作。
要允许模拟 production 命名空间中名为 app-sa 的服务账号以创建和更新部署 (deployments):
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: impersonate-app-sa
namespace: default
rules:
- apiGroups: ["authentication.k8s.io"]
resources: ["serviceaccounts"]
resourceNames: ["app-sa"]
# For service accounts, you must specify the namespace in the RoleBinding
verbs: ["impersonate:serviceaccount"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: impersonate-manage-deployments
namespace: production
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs:
- "impersonate-on:serviceaccount:create"
- "impersonate-on:serviceaccount:update"
- "impersonate-on:serviceaccount:patch"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: impersonate-app-sa
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: impersonate-app-sa
subjects:
- kind: ServiceAccount
name: deputy-controller
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: impersonate-manage-deployments
namespace: production
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: impersonate-manage-deployments
subjects:
- kind: ServiceAccount
name: deputy-controller
namespace: default
要允许 default 命名空间中的 node-impersonator 服务账号模拟名为 mynode 的节点以获取和列出 Pod:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: impersonate-node-sa
rules:
- apiGroups: ["authentication.k8s.io"]
resources: ["nodes"]
resourceNames: ["mynode"]
verbs: ["impersonate:arbitrary-node"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: impersonate-list-pods
rules:
- apiGroups: [""]
resources: ["pods"]
verbs:
- "impersonate-on:arbitrary-node:list"
- "impersonate-on:arbitrary-node:get"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: impersonate-node-sa
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: impersonate-node-sa
subjects:
- kind: ServiceAccount
name: node-impersonator
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: impersonate-list-pods
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: impersonate-list-pods
subjects:
- kind: ServiceAccount
name: node-impersonator
namespace: default
这对于需要在其节点上读取 Pod 而无需拥有集群范围 Pod 访问权限的节点代理(如 CNI 插件)是一种常见模式。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: impersonate-associated-node-identity
rules:
- apiGroups: ["authentication.k8s.io"]
resources: ["nodes"]
verbs: ["impersonate:associated-node"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: impersonate-list-pods-on-node
rules:
- apiGroups: [""]
resources: ["pods"]
verbs:
- "impersonate-on:associated-node:list"
- "impersonate-on:associated-node:get"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-agent-impersonate-node
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: impersonate-associated-node-identity
subjects:
- kind: ServiceAccount
name: node-agent
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-agent-impersonate-list-pods
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: impersonate-list-pods-on-node
subjects:
- kind: ServiceAccount
name: node-agent
namespace: kube-system
控制器将使用 downward API 获取节点名称:
env:
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
然后配置 kubeconfig 进行模拟:
kubeConfig, _ := clientcmd.BuildConfigFromFlags("", "")
kubeConfig.Impersonate = rest.ImpersonationConfig{
UserName: "system:node:" + os.Getenv("MY_NODE_NAME"),
}
从客户端的角度来看,使用受限模拟与使用传统模拟相同。你使用相同的模拟头信息:
Impersonate-User: jane.doe@example.com
或使用 kubectl:
kubectl get pods -n default --as=jane.doe@example.com
不同之处完全在于 API 服务器执行的授权检查。
impersonate 动词如果你有使用 impersonate 动词的现有 RBAC 规则,当启用特性门控时,它们将继续有效。
当发出模拟请求时,API 服务器首先检查受限模拟权限。如果这些检查失败,它会回退到检查 impersonate 权限。
每个模拟请求都会记录审计事件,以帮助跟踪模拟的使用情况。
当请求使用受限模拟时,审计事件包括一个 authenticationMetadata 对象,其中包含一个 impersonationConstraint 字段,指示哪个受限模拟动词被用于授权该请求。
对于耗时超过 500ms 的非 watch 请求,API 服务器还会向审计事件中添加 apiserver.latency.k8s.io/impersonation 注解,记录处理模拟所花费的时间(以及其他导致整体持续时间的处理程序)。
审计事件示例:
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"user": {
"username": "system:serviceaccount:default:my-controller"
},
"impersonatedUser": {
"username": "jane.doe@example.com"
},
"authenticationMetadata": {
"impersonationConstraint": "impersonate:user-info"
},
"annotations": {
"apiserver.latency.k8s.io/impersonation": "100ms"
},
"verb": "list",
"objectRef": {
"resource": "pods",
"namespace": "default"
}
}
impersonationConstraint 值指示使用了哪种模式(例如,impersonate:user-info、impersonate:associated-node)。具体操作(例如,list)可以从审计事件中的 verb 字段中确定。对于缓慢的请求,延迟注解记录了为该请求处理模拟所花费的时间(例如,100ms)。
kube-apiserver 为受限模拟公开了以下 Prometheus 指标:
apiserver_impersonation_attempts_total{mode, decision}: 一个在每次尝试模拟时递增的计数器。mode 可以是 associated-node、arbitrary-node、serviceaccount、user-info 或 legacy 之一。decision 为 allowed 或 denied。apiserver_impersonation_attempts_duration_seconds{mode, decision}: 一个跟踪解析被模拟用户所花费时间的直方图。mode 和 decision 标签具有与上述相同的值。由于处理程序内部的缓存,这反映了模拟请求的摊销延迟成本。apiserver_impersonation_authorization_attempts_total{mode, decision}: 一个在每次模拟尝试调用授权器时递增的计数器。mode 和 decision 标签具有与上述相同的值。apiserver_impersonation_authorization_attempts_duration_seconds{mode, decision}: 一个跟踪每次模拟尝试中授权器所花费时间的直方图。mode 和 decision 标签具有与上述相同的值。对于指标 apiserver_impersonation_attempts_total{mode, decision} 和 apiserver_impersonation_attempts_duration_seconds{mode, decision},当 decision 为 denied 时,mode 为空字符串。