1.1 Kubernetes 访问控制的三个层次
API Server 既是集群内部各个组件通信的中介,也是外部控制的入口。所以 kubernetes 的安全机制也是围绕保护 API Server 进行设计的。
用户访问 API Server 的方式通常有三种:kubectl、客户端库、发送 REST 请求。用户账户(自然人)和 kubernetes 服务账户(Pod)都可以被授权访问 API Server。一个请求想要到达 API Server 需要经过如下三个阶段:

- Authentication:认证。
- Authorization:授权。
- Admission Control:准入控制。
从上图也可以看出,所有的请求都会通过网关(kube-apiserver 组件)进行处理。
认证(Authentication)
认证阶段的主要目的是:识别账户身份。可以支持多种认证方式:HTTP 证书(CA)认证、HTTP Token、HTTP Base、Service Account、OpenID Connect 等。当同时指定多种认证方式时,会逐个采用这些方式对请求进行认证(认证顺序是随机的),只要通过其中一种认证方式,API Server 会认为认证成功。
在 kubernetes 集群中,默认的认证方式是 HTTP 证书认证。每个用户都有客户端证书,API Server 通过 CA 验证客户端证书。
授权(Authorization)
授权阶段的主要目的是:判断请求是否有对应的权限。授权方式有多种:AlwaysDeny,AlwaysAllow,ABAC,RBAC,Webhook,Node 等。
Authorization 是对资源(容器的计算、网络、存储资源等)的授权。当一个请求经过认证后,需要访问某种资源,授权插件通过访问策略比较该请求上下文的属性(包括:用户、资源、命名空间),根据授权规则判定该资源是否可以被客户端访问。
可以同时启用多种授权验证,只要通过其中一种授权,那么请求的授权就算是通过。如果所有的授权都没有通过,那么请求就会被拒绝(HTTP 状态码 403)。
在 kubernetes 集群中,默认的开启的授权方式是 RBAC 和 Node。
准入控制(Admission Control)
准入控制阶段的主要目的是:判断请求的操作是否符合集群的要求。可以设置“准入控制器列表”。获得授权的请求都需要通过准入控制器的检查,如果检查不通过,请求依然会被拒绝。
Admission Control 在改变资源持久化之前(资源的创建、修改、删除)的机制。
2.1 Kubernetes Account 简介
在 kubernetes 集群中,有两种类型的账户:
- User Account:用户账户。给用户使用,全局唯一,不受 namespace 命名空间的限制。这种类型的账户由外部应用进行管理。
- Service Account:服务账户。给应用程序使用,即:运行在集群中的程序访问 API Service 时使用。通常就是 Pod(准确说是运行在 Pod 中的应用)。这种类型的账户由 Kubernetes 进行管理。
这两种类型账户的区别:
- ServiceAccount 是 kubernetes 集群内部的资源对象;而 UserAccount 是存在于 kubernetes 集群外部的。
- ServiceAccount 属于某个 namespace 下,不是全局的;而 UserAccount 是全局唯一的,不属于某个 namespace 所有。
- ServiceAccount 一般被 Pod 使用,与 API Server 交互;而 UserAccount 一般被 kubectl 或 REST 请求使用。
这两种账户都会使用认证插件进行认证。UserAccount 和 ServiceAccount 的认证机制是不同的。
kubernetes 本身不提供 UserAccount 的管理和认证功能,所有的 UserAccount 都依赖于外部的用户管理系统进行存储(创建、删除、或更新),使用对应的认证插件进行认证。外部的用户管理系统可以是一个简单的用户信息文件,也可以是产品级的第三方 Id Provider(IdP),比如:IBM Cloud IAM,Google Account 等。
ServiceAccount 通过调用 API Server 进行创建(对应的是 ServiceAccount 资源对象),与特定的 namespace 命名空间绑定,在命名空间下创建相应的 Secret,该命名空间下所有的 Pods 通过 ServiceAccount 访问 API Server。
组
UserAccount 和 ServiceAccount 都可以属于一个或多个组,认证插件可以连同用户名和用户 ID返回组信息。通过组可以一次性给多个用户赋予权限。
认证插件返回的组信息是组名称的字符串。其中也有一些特殊的系统内置组:
- system:unauthenticated: 所有认证插件都不会认证客户端身份的请求。
- system:authenticated: 每个成功通过认证的用户会被分配到该组中。
- system:serviceaccounts: 包含所有在系统中的 ServiceAccount。
- system:serviceaccounts:
: 包含所有在特定命名空间中的 ServiceAccount。
UserAccount
kubernetes 集群内部并不提供对于普通用户(自然人)使用的 UserAccount 的管理机制。我们需要通过独立的外部身份认证系统对 UserAccount 进行认证,可以将外部身份认证系统集成到 kubernetes 中。外部身份认证系统可以有几种存在形式:可分发私钥的服务、用户管理系统(Keystone 或是 Google Accounts)、存储用户名密码的外部文件。
但是这里一定要注意区分的是:外部身份认证系统可以集成到 kubernetes 集群中,但是 kubernetes 集群内部依然是没有存储普通用户的资源对象。没有这个资源对象,意味着在集群内部无法创建 UserAccount,自然就更不用说修改或是删除了。
通过外部身份认证系统认证的 User,就通过了 kubernetes 安全机制中的“认证”部分,都是有权限访问集群的。但是能够在集群中进行哪些操作,依然要经过“授权”部分进行筛选,授权是由 kubernetes 本身在控制。
ServiceAccount
ServiceAccount 是运行在 Pod 中的应用程序和 API Service 进行身份认证的一种方式,它的实现需要 ServiceAccount token。这个 token 也可以用于 kubectl 访问集群,或是登录 Dashboard。
Pod 通过发送 /var/run/secrets/kubernetes.io/serviceaccount/token 目录下的文件内容来进行身份认证,这个 token 是动态生成的(由 Kubernetes Controller 用 API Server 的私钥签名生成的 JWT Secret,这个私钥可以通过参数 --service-account-private-key-file 指定),这个目录会通过加密卷的方式挂载到每个容器的文件系统中。该目录下有 3 个文件,分别为:ca.crt、namespace 以及 token。每个 Pod 都和一个 ServiceAccount 进行了关联,这代表的是运行在 Pod 中的应用程序的身份证明。
token 文件中包含的是 ServiceAccount 的认证 token,应用程序使用这个 token 连接 API Service 时,身份认证插件会对 ServiceAccount 进行身份认证,并将 ServiceAccount 的用户名传回 API Service 内部,ServiceAccount 用户名的格式如下:
system.serviceaccount:<namespace>:<service account name>
然后 API Service 会把这个用户名传递给授权插件,授权插件会判断应用程序要执行的操作是否被 ServiceAccount 允许。
2.2 Kubernetes 认证简介
认证和授权属于 Kubernetes API Service 的安全防护机制。API Service 可以配置一个或是多个认证插件(以及授权插件),API Service 接收请求会经过认证插件列表的检查,插件会检查这个请求以及请求发送方是谁。
请求可能有 3 个来源:
- UserAccount:用户通过 kubectl 输入的请求。
- ServiceAccount:Nodes 中 kubelet 的请求。
- 匿名请求:Dashboard 控制面板中的请求。
kubernetes 可以使用多种身份认证插件(包括:CA 证书、Bearer token、身份验证代理、HTTP 基本身份验证等)对 API 请求进行身份验证。
当 HTTP 请求到达 API Server 时,认证插件会将下面的属性与请求进行关联:
- Username:用户名,标识最终用户的字符串。比如:kube-admin、user@example.com。
- UID:用户的唯一标识。
- Groups:将用户归属于某个用户组。
- Extra Fields:记录用户其它有用认证信息的属性。
这些属性对于认证系统而言都没有实际的含义,只有到了授权阶段这些属性才有实际的用处。认证列表中的第一个插件可以提取请求客户端的用户名、用户 ID 及组信息,并返回给 API Service。然后 API Service 将停止调用剩余的认证插件并进入授权阶段。
在认证阶段可以启用多种身份认证方式,但是至少包括以下两种方式:
- 用户账户至少有一种 UserAccount 认证方式。
- 服务账户的 service account token。
账户身份认证的 4 种方式
Kubernetes 集群提供了 1 种针对 ServiceAccount 账户身份认证的方式:
- ServiceAccount Token 认证
Kubernetes 集群提供了 3 种针对 UserAccount 账户身份认证的方式:
- HTTPS CA 认证:基于 CA 根证书签名的双向数字证书认证方式。
- HTTP Token 认证:使用 Token 进行识别。
- HTTP Base 认证:使用 用户名:密码 进行认证。
2.3 ServiceAccount Token 认证
ca.crt、namespace 以及 token,这 3 个文件由于起到了私密凭证的作用,被称为 Kubernetes Secret 对象。Secret 属于 ServiceAccount 资源对象的一部分。一个 ServiceAccount 资源对象可以包含多个不同的 Secret 对象。
ServiceAccount 作用在单独的命名空间中,每个命名空间都会自动创建一个默认的 ServiceAccount,先来查看 ServiceAccount 列表及其详情:
$ kubectl get serviceaccounts
NAME SECRETS AGE
default 1 84d
$ kubectl describe serviceaccounts -n default
Name: default
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none> # 用于下载 image 的 secret,用于镜像下载过程中的认证
Mountable secrets: default-token-f2v5z # 这个 token 也是需要被挂载到 Pod 上的
Tokens: default-token-f2v5z # token 的名称为 default-token-sz72t
Events: <none>
一个 Pod 只能和一个 ServiceAccount 关联,而一个 ServiceAccount 可以关联多个 Pod。并且 Pod 只能和处于同一命名空间下的 ServiceAccount 进行关联。
创建 ServiceAccount
每个命名空间都有一个默认的 ServiceAccount,必要的时候我们也可以创建新的 ServiceAccount。不同的 ServiceAccount 对应到的是不同的权限,这是为了保证集群的安全性。比如有的 Pod 不需要读取集群的元数据,那么它们就应该运行在一个不允许检索或修改资源的限制权限的账户下;有的 Pod 需要读取元数据,就应该运行在只允许读取数据的账户下;需要修改权限的 Pod 就应该运行在允许修改 API 对象的账户下。
可以执行如下命令创建一个 ServiceAccount:
$ kubectl create serviceaccount foo -n user-test
serviceaccount/foo created
然后来查看这个 foo ServiceAccount 的详情:
$ kubectl describe serviceaccount foo -n user-test
Name: foo
Namespace: user-test
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: foo-token-pdwn8
Tokens: foo-token-pdwn8
Events: <none>
根据结果可以看到,已经创建了自定义的 token 密钥:foo-token-pdwn8,执行如下命令查看 token 中的数据:
$ kubectl describe secret foo-token-pdwn8 -n user-test
Name: foo-token-pdwn8
Namespace: user-test
Labels: <none>
Annotations: kubernetes.io/service-account.name: foo
kubernetes.io/service-account.uid: 78eab449-3275-46b0-86ec-c653ffe4b4a4
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1025 bytes
namespace: 9 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6InhNbEpVRDZOUDRzUGlLdndDdk9qWGlYNnplMHMwazVMQVhIdmxaTHAzajQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJ1c2VyLXRlc3QiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiZm9vLXRva2VuLXBkd244Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImZvbyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6Ijc4ZWFiNDQ5LTMyNzUtNDZiMC04NmVjLWM2NTNmZmU0YjRhNCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDp1c2VyLXRlc3Q6Zm9vIn0.UguvsZXP3ip9MIrjc0rb7aSSP-lAPm60yHoWFAZ7AlrOfPQM8NzNl_jIPQZuf9nKc4zbhE4ohYBQZ_-FNlkv32fAeKV8c_z-xaXfm7RZWGexYM09Qspgg45dajP_bdaTSNQcdTWZRaBYPty2d3LKmFLGykF0wpqhaE9LPWYeFMwwia0rrm8PFw4h1uKf8aRucAfg0ZReKTqA7H-tvQtTlw95Zsi_fOHH_-5stzLep91Rzl28jQ9IgMPY6FzobuKV4KHuGhlogIftNzsFFmyG3dgrO1ugoIzQD3rg3Hjh5-Z-hMyBzuQwUgDRL-xaHbZBfTh09vwvRdXbUO21FKTmUQ
可以看到这个 token 中有 3 个条目,分别为:ca.crt、namespace、token,这 3 个条目和默认的 ServiceAccount 的条目相同,但是对应的值肯定是不同的。
运行 Pod 时,如果不特别指明 ServiceAccount 的名称,那么就为默认的 default。也可以通过字段 serviceAccountName 指明特定的 ServiceAccount,比如:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: nginx:1.7.9
serviceAccountName: foo
2.4 HTTPS CA 认证(X509 客户端证书)
如果使用客户端证书认证,需要在 API Server 启动的时候指定 --client-ca-file=SOMEFILE 参数,API Server 通过这个 CA 文件验证请求携带客户端证书的有效性。如果验证成功,API Server 会将客户端证书 Subject 中的 CN 属性作为请求用户名。
CA 认证的流程如下所示:

双向认证的流程大致如下:
- HTTPS 通信双方的服务器端和客户端分别向 CA 机构申请证书,CA 机构下发根证书、服务端/客户端证书及私钥给申请方。
- 客户端向服务器端发起请求,服务端将服务端证书下发给客户端,客户端接收到证书后,通过私钥解密证书,并利用服务器端证书中的公钥认证证书信息比较证书中的消息,如果一致,客户端就认为这个服务器是合法的。
- 第三步,与第二步是同样的。客户端发送客户端证书给服务器端,服务端接收证书后,通过私钥解密证书,获得客户端证书公钥,并用该公钥认证证书信息,确认客户端是否合法。
- 客户端通过随机密钥加密信息,并发送加密后的信息给服务端。在服务器端和客户端协商好加密方案后,客户端产生一个随机的密钥,客户端通过协商好的加密方案加密该随机密钥,并发送该随机密钥到服务器端。服务器端接受密钥后,双方通信的所有内容都通过该随机密钥加密。
如果大家使用的是 kubeadm 搭建集群,那么在 master 节点的 /etc/kubernetes/manifests/kube-apiserver.yaml 文件中就可以看到启动 kube-apiserver Pod 的具体配置信息:
...
containers:
- command:
- kube-apiserver
- --advertise-address=172.16.98.162
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt # CA 根证书文件
- --enable-admission-plugins=NodeRestriction
- --enable-bootstrap-token-auth=true
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
- --etcd-servers=https://127.0.0.1:2379
- --insecure-port=0
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
- --requestheader-allowed-names=front-proxy-client
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --requestheader-extra-headers-prefix=X-Remote-Extra-
- --requestheader-group-headers=X-Remote-Group
- --requestheader-username-headers=X-Remote-User
- --secure-port=6443
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-cluster-ip-range=10.96.0.0/12
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt # API Server 证书文件
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key # API Server 私钥文件
image: registry.aliyuncs.com/google_containers/kube-apiserver:v1.17.0
...
2.5 HTTP Token 认证(静态 token 文件)
HTTP Token 认证是用特别复杂的 token 字符串表示身份的一种方式。每个 token 对应一个用户名和用户 ID,然后存储在 API Server 能够访问的文件中。当发起请求时,在 HTTP Header 中放入 Token,就可以对账户进行认证。
如果使用 HTTP Token 认证,需要在 API Server 启动的时候指定 --token-auth-file=SOMEFILE 参数 ,这个文件的格式为 token,用户名,用户 ID 的 CSV 文件,然后在请求时,带上 Authorization: Bearer xxx 头信息。
注意,如果修改了 API Server 的 token 文件,也是需要重启 API Server 才能让配置生效,所以使用静态 token 文件的效果也不是很好。
2.6 HTTP Base 认证(静态密码文件)
HTTP Base 认证是把 用户名:密码 用 BASE64 算法编码后的字符串放到请求头中发送给服务端,服务端在收到后进行解码,获取用户名和密码,然后进行身份鉴权。
如果使用 HTTP Base 认证,需要在 API Server 启动的时候指定 --basic-auth-file=SOMEFILE 参数,这个文件的格式为 密码,用户名,用户 ID 的 CSV 文件,然后在请求时,带上 Authorization: Basic BASE64ENCODED(USER:PASSWORD) 头信息。
需要注意的是:API Server 是一个静态的 Pod,一旦 API Server 服务启动,加载的用户名和密码就不会再改变,如果修改了源文件一定要重启 API Server 才能让配置生效。
由于这种方式不灵活、也不安全,所以在实际的过程中并不推荐使用。
评论区