引言
Kubernetes API Server 组件是 Kubernetes 具有网关性质的组件,它是 Kubernetes 集群资源操作的唯一入口,它通过 HTTP RESTful 的形式暴露服务,允许不同的用户、外部组件等访问它。我们使用 curl 命令去模拟访问 apisever 请求过程中,发生了什么。
iZj6ccqyhc7xduup9vl8mvZ :: ~ » curl --cacert /root/.minikube/ca.crt https://192.168.49.2:8443/api/v1/namespaces -k
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "namespaces is forbidden: User \"system:anonymous\" cannot list resource \"namespaces\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
"details": {
"kind": "namespaces"
},
"code": 403
}
从上面返回的结果可以看出:
- apiserver 识别这次请求的用户为 system:anonymous
- apiserver 禁止了该用户 list namespaces,并返回 403
所以通过上面的测试大致了解 apiserver 的工作机制:
- 首先,它会识别请求的用户是谁(AuthN)
- 然后,它会识别该用户具有什么样的权限(AuthZ)
而本文将重点介绍认证机制(AuthN)。
k8s认证机制的概述
在 TLS 连接建立之后,请求就进入了认证阶段。在这个阶段,一个或多个认证器模块(由管路员配置)会对请求进行认证,直到其中一个模块认证成功,之后请求的身份等信息会被记录下来,用于后续的授权和准入控制阶段。如果所有的认证器模块都没认证成功,这时候会返回给用户 401 Unauthorized 的错误码。
![[Pasted image 20230715103614.png]]
身份标识
Kubernetes API Server 支持下列两种类别的用户
- Kubernetes 管理的用户:service account,由 Kubernetes 集群创建并被集群内的 Pod 使用。
- 非 Kubernetes 管理的用户:一种是由集群管理员提供 static token 或者是 x509 证书;另一种是通过第三方如 Keystone、Google帐户和LDAP等外部身份提供商进行身份验证的用户。
除了上述用户之外,其他的都被视为匿名用户。
认证策略
Kubernetes 使用 x509 客户端证书,bearer tokens 或者认证代理去认证请求,认证成功之后会将以下属性与请求关联:
- Username:标识用户的字符串(人类可读)。
- UID:标识用户的字符串,比 Username 更具有唯一性。
- Groups:一组字符串,每个字符串标识用户所在的组。如:system:authenticated,它包含了所有已经通过认证的用户。
- Extra:map类型,存储了对后续的授权有用的额外信息。
X509 Client Certs
通过给 APIServer 配置 --client-ca-file=</path/to/file> 启动参数来使能客户端证书认证策略。该文件为 CA,用于 APIServer 校验客户端证书,如果一个客户端证书验证通过,那么证书主体的 common name 会作为用户名,证书的组织字段作为用户组。
创建私钥
openssl genrsa -out server.key 2048
生成证书请求文件
该证书标识了一个名为 foo123 的用户,归属组于 group1。
openssl req -new -key client.key -out client.csr -subj "/O=group1/CN=foo"
使用根证书签发证书请求
这里使用 minikube 的集群 ca 进行签发。
openssl x509 -req -days 365 -CA /var/lib/minikube/certs/ca.crt -CAkey /var/lib/minikube/certs/ca.key -CAserial /var/lib/minikube/certs/ca.srl -CAcreateserial -in client.csr -out client.crt
带着证书访问 apiserver 可以发现,认证已经通过了,识别出该证书对应的用户是 foo,返回 403 是因为该用户没有查看 namespaces 的权限。
root@minikube:~ curl https://localhost:8443/api/v1/namespaces --cacert /var/lib/minikube/certs/ca.crt --cert ./client.crt --key ./client.key
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "namespaces is forbidden: User \"foo\" cannot list resource \"namespaces\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
"details": {
"kind": "namespaces"
},
"code": 403
}
更对关于如何生成客户端证书的,可以参考这篇文档。 Managing Certificates
Static Token File
通过给 APIServer 配置 --token-auth-file=</path/to/file> 启动参数来使能 Bearer Tokens 认证策略。除非重启 APIServer 修改 Token,否则该 Token 不会过期。
iZj6ccqyhc7xduup9vl8mvZ :: ~ » curl --cacert /root/.minikube/ca.crt https://192.168.49.2:8443/api/v1/namespaces --header "Authorization: Bearer foo123"
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "Unauthorized",
"reason": "Unauthorized",
"code": 401
}
上图是没有配置 Static Token 的结果,虽然 reason 是未授权的意思,但是返回 401 错误码的意思表示没通过认证。
接下来给 APIServer 配置 Static Token 文件,格式为
token,user,uid,"group1,group2,group3"
foo123,foo,123,"group1,group2"
再次访问,认证已经通过了,识别出该 Bearer Token 对应的用户是 foo,返回 403 是因为该用户没有查看 namespaces 的权限。
iZj6ccqyhc7xduup9vl8mvZ :: ~ » curl --cacert /root/.minikube/ca.crt https://192.168.49.2:8443/api/v1/namespaces --header "Authorization: Bearer foo123"
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "namespaces is forbidden: User \"foo\" cannot list resource \"namespaces\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
"details": {
"kind": "namespaces"
},
"code": 403
}
Bootstrap Tokens
Bootstrap Token 是一种简单的 Bearer Token,
这种 Token 是在新建集群或者现有集群中加入节点时使用。一般是由 kubeadm 管理,以 secret 形式保存在 kube-system 命名空间,可以动态地创建删除,并且 kube-controller-manager 中 TokenCleaner 会在 Token 过期时删除。线下 IDC 集群 接入 VNode 可以使用这种安全性高的方式来进行接入。TLS 引导启动
Service Account Tokens
Service Account 在 Kubernetes 集群中有单独的 API,它被看作是集群内的用户。在每一个 namespace 下都有一个名为 default 的 service account,内容如下(1.24以上版本集群)。
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2023-06-24T10:09:33Z"
name: default
namespace: default
resourceVersion: "326"
uid: fd058d58-31f6-4e5f-9f7b-ebd41998b1c5
默认情况下,创建的 pod 如果没有特别指定 service account,都会将 namespace 下默认的 service account 以 volume 的形式挂载到容器的这个目录下 /var/run/secrets/kubernetes.io/serviceaccount/,该目录下有三个文件:token、ca.crt、namespace。其中 namespace 指定了 pod 所在的 namespace,ca .crt 用于验证 apiserver 服务端证书, token 则作为客户端身份验证,且为 jwt 形式,解码后如下:
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1721056710,
"iat": 1689520710,
"iss": "https://kubernetes.default.svc.cluster.local",
"kubernetes.io": {
"namespace": "default",
"pod": {
"name": "host-centos",
"uid": "d8616e94-5b8a-49e3-994c-82c6fd588b66"
},
"serviceaccount": {
"name": "default",
"uid": "fd058d58-31f6-4e5f-9f7b-ebd41998b1c5"
},
"warnafter": 1689524317
},
"nbf": 1689520710,
"sub": "system:serviceaccount:default:default"
}
- sub(Subject):与令牌关联的 ServiceAccount 唯一标识符。
- iss(Issuer):颁发令牌的实体,可通过 --service-account-issuer 启动参数进行配置。
- aud(Audience):接受 JWT 的一方,接受方必须检查该字段以避免攻击。
- exp(Expiration Time):token 的过期时间。
- iat(Issuer At):token 被签发的时间。
- nbf(Not Before):标识 token 在这个时间点之前都是无效的。
- kubernetes.io(additional Field):额外的字段信息,包含了 service account、pod 的一些信息。
使用上述 Bearer Token 的形式进行访问,可以发现认证已经通过。
[root@host-centos /] TOKEN=`cat /var/run/secrets/kubernetes.io/serviceaccount/token`
[root@host-centos /] curl --header "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "namespaces is forbidden: User \"system:serviceaccount:default:default\" cannot list resource \"namespaces\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
"details": {
"kind": "namespaces"
},
"code": 403
}
假如你的 Pod 没有访问集群内资源信息的需求,那么你可以通过配置 Pod.Spec.AutomountServiceAccountToken 为 false 来取消 Service Account 的挂载。
Webhook Token Authentication
它是一个 hook 点,当请求经过该模块认证时,APIServer 会根据配置文件给远端的 Service 发送 TokenReview 对象用于身份验证,远端服务验证完成之后会把认证信息回填到 TokenReview 的 Status 字段。
通过给 APIServer 配置下列启动参数来使能 Webhook 认证。
- --authentication-token-webhook-config-file:webhook 配置文件,复用 kubeconfig 配置,用来描述远端服务和自身客户端信息。
- --authentication-token-webhook-cache-ttl:缓存身份验证决策的时间。默认为两分钟。
- --authentication-token-webhook-version:确定是使用 authentication.k8s.io/v1beta1 还是 authentication.k8s.io/v1 TokenReview 对象,默认为v1beta1。
Webhook 配置文件
apiVersion: v1
kind: Config
clusters:
- name: name-of-remote-authn-service
cluster:
certificate-authority: /path/to/ca.pem
server: https: //authn.example.com/authenticate
users:
- name: name-of-api-server
user:
client-certificate: /path/to/cert.pem
client-key: /path/to/key.pem
current-context: webhook
contexts:
- context:
cluster: name-of-remote-authn-service
user: name-of-api-server
name: webhook
TokenReview Spec
{
"apiVersion": "authentication.k8s.io/v1",
"kind": "TokenReview",
"spec": {
"token": "014fbff9a07c...",
"audiences": ["https://myserver.example.com", "https://myserver.internal.example.com"]
}
}
TokenReview Status
{
"apiVersion": "authentication.k8s.io/v1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
"username": "janedoe@example.com",
"uid": "42",
"groups": ["developers", "qa"],
"extra": {
"extrafield1": [
"extravalue1",
"extravalue2"
]
}
},
"audiences": ["https://myserver.example.com"]
}
}
Authenticating Proxy
APIServer 可以通过配置以下启动参数来识别请求中特定的 Headers 字段作为用户的信息。
- --requestheader-username-headers:按顺序检查请求的 Headers 字段,第一个匹配的将作为用户标识。
- --requestheader-group-headers:按顺序检查请求的 Headers 字段,所有匹配的都将作为用户的组。
- --requestheader-extra-headers-prefix:按顺序检查请求的 Headers 字段,所有前缀匹配的字段都将作为用户身份的附加信息,将用于后续的授权模块。
假如 APIServer 的启动参数配置如下
--requestheader-username-headers=X-Remote-User
--requestheader-group-headers=X-Remote-Group
--requestheader-extra-headers-prefix=X-Remote-Extra-
那么当请求带着下列 Headers 来访问 APIServer 时
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
为了防止 header 欺骗,认证代理访问 apiserver 时必须提供证书,可以通过给 APIServer 配置启动参数 --requestheader-client-ca-file 用于校验认证代理的证书有效性,--requestheader-allowed-names 用于校验证书的 Common Name。
参考文档
https://kubernetes.io/docs/reference/access-authn-authz/authentication/