k8s 访问控制之用户认证

发布时间 2023-07-26 18:36:14作者: 小吉猫

用户账户

Kubernetes系统上的用户账户及用户组的实现机制与常规应用略有不同。Kubernetes集群将那些通过命令行工具kubectl、客户端库或者直接使用RESTful接口向API Server发起请求的客户端上的请求主体分为两个不同的类别:现实中的“人”和Pod对象,它们的用户身份分别对应用户账户(User Account,也称为普通用户)和服务账户(Service Account,简称SA)。
用户账户通常用于复杂的业务逻辑管控,作用于系统全局,因而名称必须全局唯一。Kubernetes并不会存储由认证插件从客户端请求中提取的用户及所属的组信息,因而也就没有办法对普通用户进行身份认证,它们仅仅用于检验该操作主体是否有权限执行其所请求的操作。相比较来说,服务账户则隶属于名称空间级别,仅用于实现某些特定操作任务,因此功能上要轻量得多。这两类账户都可以隶属于一个或多个用户组。

普通账户

其使用主体往往是“人”,一般由外部的用户管理系统存储和管理,Kubernetes本身并不维护这一类的任何用户账户信息,它们不会存储到API Server之上,仅仅用于检验用户是否有权限执行其所请求的操作。

服务账户

其使用主体是“应用程序”,专用于为Pod资源中的服务进程提供访问Kubernetes API时的身份标识(identity);ServiceAccount资源通常要绑定到特定的名称空间,它们由API Server自动创建或通过API调用,由管理员手动创建,通常附带着一组访问API Server的认证凭据——Secret,可由同一名称的Pod应用访问API Server时使用。

用户组

system:unauthenticated:未能通过任何一个授权插件检验的账户的、所有未通过认证测试的用户统一隶属的用户组。
system:authenticated:认证成功后的用户自动加入的一个专用组,用于快捷引用所有正常通过认证的用户账户。
system:serviceaccounts:所有名称空间中的所有ServiceAccount对象。
system:serviceaccounts:<namespace>:特定名称空间内所有的ServiceAccount对象。

身份认证策略

Kubernetes 通过身份认证插件利用客户端证书、持有者令牌(Bearer Token)或身份认证代理(Proxy) 来认证 API 请求的身份。HTTP 请求发给 API 服务器时,插件会将以下属性关联到请求本身:
  Username:用来辩识最终用户的字符串。常见的值可以是 kube-admin 或 jane@example.com。
  UID:用来辩识最终用户的字符串,旨在比用户名有更好的一致性和唯一性。
  Groups:取值为一组字符串,其中各个字符串用来标明用户是某个命名的用户逻辑集合的成员。 常见的值可能是 system:masters 或者 devops-team 等。
  Extra:一组额外的键-值映射,键是字符串,值是一组字符串; 用来保存一些鉴权组件可能觉得有用的额外信息。
所有(属性)值对于身份认证系统而言都是不透明的, 只有被鉴权组件解释过之后才有意义。

你可以同时启用多种身份认证方法,并且你通常会至少使用两种方法:

  针对服务账号使用服务账号令牌
  至少另外一种方法对用户的身份进行认证
  
当集群中启用了多个身份认证模块时,第一个成功地对请求完成身份认证的模块会直接做出评估决定。 API 服务器并不保证身份认证模块的运行顺序。

对于所有通过身份认证的用户,system:authenticated 组都会被添加到其组列表中。

与其它身份认证协议(LDAP、SAML、Kerberos、X509 的替代模式等等) 都可以通过使用一个身份认证代理或身份认证 Webhoook 来实现。

X509 客户证书

客户端在请求报文中携带X.509格式的数字证书用于认证,其认证过程类似于HTTPS协议通信模型;认证通过后,证书中的主体标识(Subject)将被识别为用户标识,其中的字段CN(Common Name)的值是用户名,字段O(Organization)的值是用户所属的组。该认证方式可通过--client-ca-file=SOMEFILE选项启用。

示例

openssl req -new -key jbeda.pem -out jbeda-csr.pem -subj "/CN=jbeda/O=app1/O=app2"
此命令将使用用户名 jbeda 生成一个证书签名请求(CSR),且该用户属于 "app1" 和 "app2" 两个用户组。

静态令牌文件

即保存用于认证的令牌信息的静态文件,由kube-apiserver的命令行选项--token-auth-file加载,令牌会长期有效,并且在不重启 API 服务器的情况下无法更改令牌列表;HTTP协议的客户端能基于承载令牌(Authorization: Bearer <token>标头)对静态令牌文件进行身份验证,它将令牌编码后通过请求报文中的Authorization头部承载并传递给API Server即可;建议仅将该认证方式用于非生产性环境中。
令牌文件是一个 CSV 文件,包含至少 3 个列:令牌、用户名和用户的 UID。 其余列被视为可选的组名。
例如:如果持有者令牌为 31ada4fd-adec-460c-809a-9e56ceb75269,则其出现在 HTTP 头部时如下所示:
  Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269

服务账号令牌

服务账号(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 上。

启动引导令牌

为了支持平滑地启动引导新的集群,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 用来)构造合适的鉴权策略, 以完成启动引导新集群的工作。

OpenID Connect(OIDC)令牌

OpenID Connect 是一种 OAuth2 认证方式, 被某些 OAuth2 提供者支持,例如 Azure 活动目录、Salesforce 和 Google。 协议对 OAuth2 的主要扩充体现在有一个附加字段会和访问令牌一起返回, 这一字段称作 ID Token(ID 令牌)。 ID 令牌是一种由服务器签名的 JWT 令牌,其中包含一些可预知的字段, 例如用户的邮箱地址,

要识别用户,身份认证组件使用 OAuth2 令牌响应中的 id_token(而非 access_token)作为持有者令牌。 关于如何在请求中设置令牌

Webhook 令牌身份认证

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”。
Webhook身份认证是用于验证承载令牌的钩子;HTTP协议的身份验证允许将服务器的URL注册为Webhook,并接收带有承载令牌的POST请求进行身份认证;客户端使用kubeconfig格式的配置文件,在文件中,users指的是API Server的Webhook,而clusters则指的是API Server。

示例

# 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 服务器可以配置成从请求的头部字段值(如 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 服务器会将与所给前缀匹配的头部字段过滤出来,去掉其前缀部分,将剩余部分转换为小写字符串, 并在必要时执行百分号解码后, 构造新的附加信息字段键名。原来的头部字段值直接作为附加信息字段的值。
为了防范头部信息侦听,在请求中的头部字段被检视之前, 身份认证代理需要向 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 用户组执行显式的权限判定,所以之前的为用户 * 或用户组 * 赋予访问权限的策略规则都不再包含匿名用户。

用户伪装

一个用户可以通过伪装(Impersonation)头部字段来以另一个用户的身份执行操作。 使用这一能力,你可以手动重载请求被身份认证所识别出来的用户信息。 例如,管理员可以使用这一功能特性来临时伪装成另一个用户,查看请求是否被拒绝, 从而调试鉴权策略中的问题,
带伪装的请求首先会被身份认证识别为发出请求的用户, 之后会切换到使用被伪装的用户的用户信息。

  用户发起 API 调用时 同时 提供自身的凭据和伪装头部字段信息
  API 服务器对用户执行身份认证
  API 服务器确认通过认证的用户具有伪装特权
  请求用户的信息被替换成伪装字段的值
  评估请求,鉴权组件针对所伪装的用户信息执行操作
以下 HTTP 头部字段可用来执行伪装请求:

Impersonate-User:要伪装成的用户名
Impersonate-Group:要伪装成的用户组名。可以多次指定以设置多个用户组。 可选字段;要求 "Impersonate-User" 必须被设置。
Impersonate-Extra-<附加名称>:一个动态的头部字段,用来设置与用户相关的附加字段。 此字段可选;要求 "Impersonate-User" 被设置。为了能够以一致的形式保留, <附加名称>部分必须是小写字符, 如果有任何字符不是合法的 HTTP 头部标签字符, 则必须是 utf8 字符,且转换为百分号编码。
Impersonate-Uid:一个唯一标识符,用来表示所伪装的用户。此头部可选。 如果设置,则要求 "Impersonate-User" 也存在。Kubernetes 对此字符串没有格式要求。

为客户端提供的对身份验证信息的 API 访问

# curl -s -k -X POST https://192.168.174.100:6443/apis/authentication.k8s.io/v1beta1/selfsubjectreviews
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}

参考文档

https://kubernetes.io/docs/reference/access-authn-authz/authentication/