通过前面的章节学习,你已经学会了使用kubectl
命令行,或者直接发送 REST 请求,以及使用各种语言的 client 库来跟 APIServer 进行交互。那么你是否知道在这其中Kubernetes 是如何对这些请求进行认证、授权的呢?这节课,我们就来一探究竟。
任何请求访问 Kubernetes 的 kube-apiserver 时,都要依次经历三个阶段:认证(Authentication,有时简写成 AuthN)、授权(Authorization,有时简写成 AuthZ)和准入控制(Admission Control)。
其中,认证这个概念比较好理解,就是做身份校验,解决“你是谁?”的问题。
授权负责做权限管控,解决“你能干什么?”的问题,给予你能访问 Kubernetes 集群中的哪些资源以及做哪些操作的权利,比如你是否能创建一个 Pod,是否能删除一个 Pod。
准入控制,从字面上你可能不太好理解。其实就是由一组控制逻辑级联而成,对对象进行拦截校验、更改等操作。比如你打算在一个名为 test 的 namespace 中创建一个 Pod,如果这个 namespace 不存在,集群要不要自动创建出来?或者直接拒绝掉该请求?这些逻辑都可以通过准入控制进行配置。
当然如果你的 Kubernetes 集群开启了 HTTP 访问,就不再需要认证和授权流程了。显然这种配置相当于敞开大门,风险太大了,所以在生产环境我建议你不要这么配置。
上述三个阶段,任何一个阶段验证失败都会导致该请求被拒绝访问,我们现在深入认识一下每个阶段。
认证
在认证阶段最重要的就是身份(Identity),我们需要从中获取到用户的相关信息。通常,Identity 信息可以通过 User 或者 Group 来定义,但是 Kubernetes 中其实并没有相关定义,所以你无法通过 Kubernetes API 接口进行创建、查询和管理。
Kubernetes 认为 User 这类信息由外部系统来管理,自己并不负责管理和设计,这样可以避免重复定义用户模型,方便第三方用户权限平台进行对接。
所以 Kuberentes 是如何做身份认证的呢?这里我们简单介绍一下 Kubernetes 中几种常见的用户认证方式。
x509 证书
Kubernetes 使用mTLS进行身份验证,通过解析请求使用的 客户端(client)证书,将其中的 subject 的通用名称(Common Name)字段(例如"/CN=bob")作为用户名。Kubernetes 集群各个组件间相互通信,都是基于 x509 证书进行身份校验的。关于证书的创建、查看等,可以参考官方文档。
比如使用openssl
命令行工具生成一个证书签名请求(CSR):
openssl req -new -key zhangsan.pem -out zhangsan-csr.pem -subj "/CN=zhangsan/O=app1/O=app2"
这条命令会使用用户名zhangsan生成一个 CSR,且它属于 "app1" 和 "app2" 两个用户组,然后我们使用 CA 证书根据这个 CSR 就可以签发出一对证书。当用这对证书去访问 APIServer 时,它拿到的用户就是zhangsan了。
Token
我们接着来看基于 Token 的验证方式,它又可以分为几种类型。
第一种是用户自己提供的静态 Token,可以将这些 Token 写到一个 CSV 文件中,其中至少包含 3 列:分别为 Token、用户名和用户 ID。你可以增加一个可选列包含 group 的信息,比如:
token1,user1,uid1,"group1,group2,group3"
APIServer 在启动时会通过参数--token-auth-file=xxxx
来指定读取的 CSV 文件。不过这种方式,实际环境中基本上不会使用到。毕竟这种静态文件不方便修改,每次修改后还需要重新启动 APIServer。
还有一种是ServiceAccount Token。这是 Kubernetes 中使用最为广泛的认证方式之一,主要用来给 Pod 提供访问 APIServer 的权限,通过使用 Volume 的方式挂载到 Pod 中。我们之前讲 Pod、Volume 的时候提到过,你也可以参考这份文档重温下如何为 Pod 配置 ServiceAccount。
当你创建一个 ServiceAccount 的时候,kube-controller-manager 会自动帮你创建出一个Secret来保存 Token,比如:
$ kubectl create sa demo
serviceaccount/demo created
? ~ kubectl get sa demo
NAME SECRETS AGE
demo 1 6s
? ~ kubectl describe sa demo
Name: demo
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: demo-token-fvsjg
Tokens: demo-token-fvsjg
Events: <none>
可以看到这里自动创建了一个名为demo-token-fvsjg
的 Secret,我们来查看一下其中的内容:
$ kubectl get secret demo-token-fvsjg
NAME TYPE DATA AGE
demo-token-fvsjg kubernetes.io/service-account-token 3 27s
$ kubectl describe secret demo-token-fvsjg
Name: demo-token-fvsjg
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: demo
kubernetes.io/service-account.uid: f8fe4799-9add-4a36-8c29-a6b2744ba9a2
Type: kubernetes.io/service-account-token
Data
====
token: eyJhbGciOiJSUzI1NiIsImtpZCI6Ildhc3plOFE0UXBlRE8xTFdsRThRVXktRF93T0otcW55VXI3R0RvV1IzVjgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlbW8tdG9rZW4tZnZzamciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVtbyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImY4ZmU0Nzk5LTlhZGQtNGEzNi04YzI5LWE2YjI3NDRiYTlhMiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlbW8ifQ.t0sNk8PiRIgMXaauzM8v8eW_AF7J5Su_TpyURaeBQX0BuFDpU-rrTC6h9sjetAQOLa6iLFsrG2tqH04pNo2N6TB73-K-M54veVpP6FGhceLO0Vsz_iTJuOsnkS_6agqfW0UrtaY8_oCk1OZipdbTJYZlmwlb6opcfP1j7bFHCdDbwfbWx8ilHqTaJ5_r7zrdhxsmC5ogtBRDaJfEYmsIBZSgnt_0qMHNj9L47n2mj_wo-aQQ25o0lbO_7RSeVA17kkgbAJ2wzwvv8-i3f7K23DSxab3k8trbuIRt1R3Gl33fv5WIwiz9okYIFfus4pe0MCtjBxxTMa526cdezKS4LQ
ca.crt: 1025 bytes
namespace: 7 bytes
Data 里面的 token 我们可以到https://jwt.io/中 Decode 出来:
从解析出来的 Payload 中可以看到, ServiceAccount 所属的 namespace、name 等信息。
除此之外,还支持Bootstrap Token、OpeID Connect Token (OIDC Token)、Webhook Token等,在此不一一介绍。
值得一提的是,Bootstrap Token 是 kubeadm 引入的方便创建新集群所使用的 Token 类型,通过如下类似的命令就可以将其加入新的节点中:
kubeadm join --discovery-token abcdef.1234567890abcdef
不管是上面哪种 Token,我们都可以拿来向 APIServer 发送请求,只需要向 HTTP 请求的 Header 中添加一个键为 Authorization ,值为Bearer THETOKEN
的字段,类似如下:
Authorization: Bearer this-is-a-very-very-very-long-token
还有一种是静态用户密码文件,在 1.16 版本以前,Kubernetes 支持指定一个 CSV 文件,里面包含了用户名以及密码等信息。但是在 1.16 版本开始已经被废弃掉了,这里我提出来是想告诉你请不要再使用这种方式了。
我介绍的这些也是使用最多的认证方式,通过认证就来到了授权阶段。
授权
Kubernetes 内部有多种授权模块,比如 Node、ABAC、RBAC、Webhook。授权阶段会根据从 AuthN 拿到的用户信息,依次按配置的授权次序逐一进行权限验证。任一授权模块验证通过,即允许该请求继续访问。
我们这里简要讲一下 ABAC 和 RBAC 这两种授权模式。
ABAC
ABAC (Attribute Based Access Control) 是一种基于属性的访问控制,可以给 APIServer 指定一个 JSON 文件--authorization-policy-file=SOME_FILENAME
,该文件描述了一组属性组合策略,比如:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "zhangsan", "namespace": "*", "resource": "pods", "readonly": true}}
这条策略表示,用户 zhangsan 可以以只读的方式读取任何 namespace 中的 Pod。这里有一份示例文件,你可以参考一下。
不过在实际使用中,ABAC 使用得比较少,跟我们上面 AuthN 中提到的静态 Token 一样,不方便修改、更新,每次变更后都需要重启 APIServer。所以在实际使用中,RBAC 最常见,使用更广泛。
RBAC
相对而言,RBAC 使用起来就非常方便了,通过 Kubernetes 的对象就可以直接进行管理,也便于动态调整权限。
RBAC 中引入了角色,所有的权限都是围着这个角色进行展开的,每个角色里面定义了可以操作的资源以及操作方式。在 Kubernetes 中有两种角色,一种是 namespace 级别的 Role ,一种是集群级别的 ClusterRole 。
在 Kubernetes 中有些资源是 namespace 级别的,比如 Pod、Service 等,而有些则属于集群级别,比如 Node、PV 等。所以有了 Role 和 ClusterRole 两种角色。
我们来看个例子:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # 空字符串""表明使用core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
这样一个角色表示可以访问 default 命名空间下面的 Pods,并可以对其进行 get、watch 以及 list 操作。
ClusterRole 除了可以定义集群方位的资源外,比如 Node,还可以定义跨 namespace 的资源访问,比如你想访问所有命名空间下面的 Pod,就可以这么定义:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
# 鉴于ClusterRole是集群范围对象,所以这里不需要定义"namespace"字段
name: pods-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
RBAC 中定义这些角色包含了一组权限规则,这些规则纯粹以累加形式组合,没有互斥的概念。
定义好角色,我们就可以做权限绑定了。这里分别有两个 API 对象,RoleBinding 和 ClusterRoleBinding。角色绑定就是把一个权限授予一个或者一组用户,例如:
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定使得用户 "jane" 或者 "manager"组(Group)能够读取 "default" 命名空间中的 Pods
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane # 名称区分大小写
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: manager # 名称区分大小写
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role # 这里可以是 Role,也可以是 ClusterRole
name: pods-reader # 这里的名称必须与你想要绑定的 Role 或 ClusterRole 名称一致
apiGroup: rbac.authorization.k8s.io
这里定义的 RoleBinding 对象在 default 命名空间中将 pods-reader 角色授予用户 jane 和组 manager。 这一授权将允许用户 jane 和 manager 组中的各个用户从default 命名空间中读取到 Pod。
好了,最后就是准入控制了。
准入控制
准入控制可以帮助我们在 APIServer 真正处理对象前做一些校验以及修改的工作。
这里官方列出了很多准入控制组件,官方已经将使用方法写得很清楚了,你可以根据自己的需要,选择合适的准入控制插件。
小结
作为一个企业级的容器管理调度平台,Kubernetes 在 Auth 方面设计得很完善,支持多种后端身份认证授权系统,比如 LDAP (Lightweight Directory Access Protocol)、Webhook 等。AuthN 负责完成对用户的认证,并获取用户的相关信息(比如 Username、Groups 等),而 AuthZ 则会根据 AuthN 返回的用户信息,根据配置的授权模式进行权限校验,来决定是否允许对某个 API 资源的操作请求。