Kubernetes 主要通过 API Server 对外提供服务,对于这样的系统来说,如果不加以安全限制,那么可能导致请求被滥用,甚至导致整个集群崩塌。
鉴于此,Kubernetes 对于访问 API 的用户提供了相应的安全控制:认证和授权。认证解决用户是谁的问题,授权解决用户能做什么的问题。只有通过合理的权限控制,才能够保证整个集群系统的安全可靠。
下图是 API 访问需要经过的三个步骤,它们分别是:认证、授权和准入,准入不在这章节讨论,它更多专注的是资源的管理。
认证
认证(Authentication)是指用户是否可以登录 Kubernetes,即是否可以向 API Server 发送请求。
在 Kubernetes 中,有两类用户:
- Normal Users:外部用户
- Service Accounts:内部用户
认证用户
Normal Users
Normal Users 独立于 Kubernetes,不归其管理,这类用户可以是:
- 可以分发 private keys 的管理员(真人)
- 提供用户服务的第三方厂商,比如 Google Accounts
- 保存用户名和密码的列表文件
如果用户都不在 Kubernetes 中,都不归它管,那它是如何进行认证的呢?
对于一般的应用系统来说,用户提供用户名和密码,服务端收到过后会在数据库中进行检查是否存在并有效,如果有就表示鉴权成功,反之失败。
那对于 Kubernetes 来说,是如何实现的呢?
尽管无法通过 API 调用来添加普通用户,Kubernetes 巧妙的通过证书来进行用户认证。也就是说,不管任何用户,只要能提供有效的证书就能通过 Kubernetes 用户认证。
通过用户认证过后,Kubernetes 会把证书中的 CN 作为用户名(比如证书中”/CN=joker“,则用户名就是 Joker),把 Organization 作为用户组,然后由用户名和用户组绑定的 Role 来决定用户的权限。
Service Accounts
Service Accounts 由 Kubernetes 管理,它们被绑定到特定的 namespace,其可以通过 API Server 自己创建,也可以通过调用 API 来创建,比如使用 kubectl 客户端工具。
与 Normal Users 不同,Service Accounts 存在对应的 Kubernetes 对象,当创建 Service Accounts,会创建相应的 Secret,里面保存对应的密码信息(在 1.24.x 版本中,不会创建对应的 secret)。当创建的 Pod 指定了一个 Service Account,其 Secret 会被 Mount 到 Pod 中,Pod 中的进程就可以访问 Kubernetes API 了。
认证策略
Kubernetes 有以下几种鉴权方法:
- 客户端证书
- 不记名令牌
- 身份认证代理
- 通过鉴权插件的 HTTP 基本认证机制
当 HTTP 请求发送到 API Server 时,Kubernetes 会从 Request 中关联以下信息:
Username:用户名,用来辨识最终用户的字符串,比如 kube-admin 或jane@example.com
UID:用户 ID,代表用户的唯一 ID
Groups:用户组,代表逻辑相关的一组用户,
Extra Fields,扩展字段,鉴权可能需要的其他信息
需要注意的是,当多种鉴权方法同时启用时,第一个鉴权方法鉴权成功即通过验证,其它方法不再鉴权,同时 api-server 也不保证鉴权方法的顺序。所有鉴权成功的用户都会加到 group “system:authenticated”中。
客户端证书
当我们使用客户端证书进进行认证时,需要向 Kubernetes 提供有效的证书,这个证书可以是外部证书,也可以是 Kubernetes 自己审批的证书。
如果是外部证书,就需要在 API Server 启动的时候用--client-ca-file=SOMEFILE参数引入外部证书的 CA 等信息,用来验证客户端证书的有效性。
当认证通过后,Kubernetes 会把证书的 Subject CN 作为用户名,Organization 作为用户组(Group)。
不记名令牌
当使用不记名令牌(Bearer token)来对某 HTTP 客户端执行身份认证时,API 服务器希望看到一个名为 Authorization 的 HTTP 头,其值格式为 Bearer。不记名令牌(Bearer token)必须是一个可以放入 HTTP 头部值字段的字符序列,至多可使用 HTTP 的编码和引用机制。例如:如果持有者令牌为 31ada4fd-adec-460c-809a-9e56ceb75269,则其出现在 HTTP 头部时如下所示:
Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269
在 Kubernetes 中,主要有以下几种使用不记名令牌(Bearer token)的方法:
- Static Token File(静态令牌)
- Service Account Tokens(服务账号令牌)
- OpenID Connect Tokens(OIDC 令牌)
Static Token File
当使用静态令牌的时候,API Server 会通过--token-auth-file=SOMEFILE从外部引入一个 CSV 文件,API Server 会从这个文件中读取对应的用户名和用户组。
当客户端进行请求时,API Server 把请求 Header 中的 Bearer tokens 和文件中的 token 进行比较,然后判断 Token 是否有效。
Service Account Tokens
当手动或者自动创建 Service Account 的时候,Kubernetes 会自动创建一个 Service Account Token,在 v1.24 版本之前,会对应创建一个 secret 保存到对应的 namespace 中,在 v1.24 版本之后不会再单独创建 secret,而是在启动 Pod 的时候,由 kubelet 通过 TokenRequest API 生成一个 Token 挂载到 Pod 中。
上文提到,Service Account 主要是为 Pods 提供访问 API Server 的功能,当 Pod 创建过后,Service Account Token 就会被 Mount 到 Pod 中,此时 Pod 就拥有访问 API Server 的能力。
当然,Service Account Token 除了用在 Pod 上,在外部也可以使用,在《Kubernetes 集群管理》中的集群安装章节,有介绍使用 Token 访问 Kubernetes Dashboard,这时候使用的也是 Service Account Token。
OpenID Connect Tokens
OpenID Connect 是一种 OAuth2 认证方式,Kubernetes 可以利用 OAuth2 Token Response 中的 ID Token 作为 Bearer Token 进行认证访问。
其流程如下:
- 登录到你的身份服务(Identity Provider)
- 你的身份服务将为你提供 access_token、id_token 和 refresh_token
- 在使用 kubectl 时,将 id_token 设置为 --token 标志值,或者将其直接添加到 kubeconfig 中
- kubectl 将你的 id_token 放到一个称作 Authorization 的头部,发送给 API 服务器
- API 服务器将负责通过检查配置中引用的证书来确认 JWT 的签名是合法的
- 检查确认 id_token 尚未过期
- 确认用户有权限执行操作
- 鉴权成功之后,API 服务器向 kubectl 返回响应
- kubectl 向用户提供反馈信息
不管用户通过哪种方式进行认证,认证通过并不代表就有操作权限,仅仅只是通过第一条防线而已,下一步就要进行鉴权,用来决定用户是否有具体的操作权限。
鉴权
鉴权流程
当请求通过认证(Authentication)过后,就会进入鉴权(Authorization)阶段。在这个阶段 Kubernetes 会检查请求是否有权限访问需要的资源,如果有权限则开始处理请求,反之则返回权限不足。
API Server 会检查所有 Policy 来检查是否存在 Policy 允许请求中的动作,存在则允许请求执行,否则会拒绝执行并返回 403 错误。当配置了多个授权模块的时候,请求会按顺序校验每一个模板,如果其中任一模块校验不通过,则请求会被拒绝,不再进行后续的校验。
Kubernetes 在做鉴权时,主要检查以下信息:
- user:同鉴权中检查的信息相同
- group:同鉴权中检查的信息相同
- extra:同鉴权中检查的信息相同
- API:是否为 Api 资源
- Request path:非资源(API 资源)endpoint,比如/healthz
- API request verb:API 动作,比如 get, list, create, update, patch,对某个资源的具体动作,比如,列出所有的 pod
- HTTP request verb:用于非资源的请求中的 HTTP 动作,比如 get, post, put
- Resource:请求访问的资源名字或 ID
- Subresource:请求访问的子资源名字或 ID
- Namespace:资源所在的名字空间
- API group:请求访问的 API group,API group 指控制相关资源的一组 Api,如果未指定则代表 core API group
鉴权模块
Kubernetes 提供了以下 4 种鉴权模式:
- Node:一种特殊的授权模块,基于 Node 上运行的 Pod 为 Kubelet 授权
- ABAC:基于属性的访问控制
- RBAC:基于角色的访问控制
- Webhook:HTTP 请求回调,通过一个 WEB 应用鉴定是否有权限进行某项操作
这里只会介绍 RBAC——基于角色的访问控制。在实际中,这种模式使用的场景比较多。
RBAC
RBAC 是 Kubernetes 中常用的鉴权模式,其基本概念如下:
Rule:规则,一组属于不同 API Group 的操作集合;
Role:角色,用于定义一组对 Kubernetes API 对象操作的一组规则,作用于当个 namespace;
ClusterRole:集群角色,该角色不受 namespace 的限制;
Subject:被作用者,也就是规则作用的对象;
RoleBinding:将角色和被作用者进行绑定,作用于当个 namespace;
ClusterRoleBinding:将集群角色和作用者进行绑定,不受 namespace 限制;
Role 和 ClusterRole
Role 和 ClusterRole 中定义一组相关权限的规则,这些权限是累加的(不存在拒绝某操作的规则)。其中 Role 是 Namespace 级别的,而 ClusterRole 是集群级别的。
Role 的定义如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: role-demo
namespace: devops
rules:
- apiGroups: [""]
resources: ["pods", "deployment"]
verbs: ["creat", "delete", "watch", "list", "get"]
其含义是:它允许被作用者在 devops 的 namespace 中对 pod 和 deployment 有 creat,delete,watch,list,get 操作。由于 Role 是 Namespace 级别,所以上面的规则只对 devops 的 namespace 有效。
ClusterRole 的定义如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-role-demo
rules:
- apiGroups: [""]
resources: [""]
verbs: ["watch", "list", "get"]
其含义是对所有资源都有 watch、list、get 的操作权限。
如果要定义所有权限,可以将 verbs 字段定义如下:
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
RoleBinding 和 ClusterRoleBinding
角色绑定将一个角色中定义的各种权限授予一个或者一组用户。角色绑定包含了一组相关主体(即 subject, 包括用户——User、用户组——Group、或者服务账户——Service Account)以及对被授予角色的引用。在命名空间中可以通过 RoleBinding 对象授予权限,而集群范围的权限授予则通过 ClusterRoleBinding 对象完成。
RoleBinding 可以引用在同一命名空间内定义的 Role 对象。如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: joker-rolebinding
namespace: devops
subjects:
- kind: User
name: joker
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: role-demo
apiGroup: rbac.authorization.k8s.io
这就给 joker 用户和 role-demo 的 Role 建立了绑定关系,其中 joker 这个 User 只是一个授权系统中的逻辑概念,它需要通过外部认证服务,比如 Keystone,或者直接给 API Server 指定一个用户名,密码文件。
上面的 YAML 文件中其中一个重要的字段是 Subjects 字段,它定义"被作用者",其中的 kind 表示被作用者的类型,其有以下三种类型:
- User:用户,这是由外部独立服务进行管理的,管理员进行私钥的分配,用户可以使用 KeyStone 或者 Goolge 帐号,甚至一个用户名和密码的文件列表,对于用户的管理集群内部没有一个关联的资源对象,所以用户不能通过集群内部的 API 来进行管理。
- Group:组,这是用来关联多个账户,集群中有一个默认的组,比如 cluster-admin。
- ServiceAccount:服务帐号,通过 Kubernetes API 来管理的一些用户帐号,和 namespace 进行关联的,适用于集群内部运行的应用程序,需要通过 API 来完成权限认证,所以在集群内部进行权限操作,我们都需要使用到 ServiceAccount。
另外一个重要字段是 roleRef,它定义 RoleBing 对象可以直接通过 Role 的名字来引用我们定义的 Role 对象,从而定义被作业者和角色之间的绑定关系。
RoleBinding 是 namespace 级别的,如果是集群级别则用 ClusterRoleBinding,如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: joker-clusterrolebinding
subjects:
- kind: User
name: joker
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-role-demo
apiGroup: rbac.authorization.k8s.io
上面定义的就是 joker 这个用户对所有 namespace 里的资源都有 watch,list,get 的操作权限。
Service Account
Service Account 也是一种账号,但它并不是给 Kubernetes 集群的用户(系统管理员、运维人员、租户用户等)用的,而是给运行在 Pod 里的进程用的,它为 Pod 里的进程提供了必要的身份证明。
我们通过一个例子来了解 ServiceAccount 的授权过程。
(1)首先定义一个 ServiceAccount:
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: devops
name: sa-demo
一个简单的 ServiceAccount 只需要简单的 namespace 和 name 即可。
(2)编写 RoleBinding 的 YAML 文件来为这个 ServiceAccount 分配权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: role-demo
namespace: devops
rules:
- apiGroups: [""]
resources: ["pods", "deployment"]
verbs: ["creat", "delete", "watch", "list", "get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: sa-rolebinding
namespace: devops
subjects:
- kind: ServiceAccount
name: sa-demo
namespace: devops
roleRef:
kind: Role
name: role-demo
apiGroup: rbac.authorization.k8s.io
然后我们创建上面定义的 YAML 文件,查看创建完成后的信息:
$ kubectl get sa -n devops
NAME SECRETS AGE
default 0 6m25s
sa-demo 0 6m25s
$ kubectl get role -n devops
NAME CREATED AT
role-demo 2022-07-06T04:27:02Z
$ kubectl get rolebinding -n devops
NAME ROLE AGE
sa-rolebinding Role/role-demo 6m50s
现在创建一个 Pod 并使用 sa-demo Service Account。
apiVersion: v1
kind: Pod
metadata:
name: pod-sa-demo
namespace: devops
spec:
serviceAccountName: sa-demo
containers:
- name: pod-sa-demo
image: nginx
imagePullPolicy: IfNotPresent
查看 Pod 信息如下:
$ kubectl get po -n devops pod-sa-demo -oyaml
apiVersion: v1
kind: Pod
metadata:
annotations:
cni.projectcalico.org/containerID: c0820de4319bb6915602c84132ff83a63f62abaa1e9c706bad04e64661455d30
cni.projectcalico.org/podIP: 172.16.51.225/32
cni.projectcalico.org/podIPs: 172.16.51.225/32
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"pod-sa-demo","namespace":"devops"},"spec":{"containers":[{"image":"nginx","imagePullPolicy":"IfNotPresent","name":"pod-sa-demo"}],"serviceAccountName":"sa-demo"}}
creationTimestamp: "2022-07-06T04:30:13Z"
name: pod-sa-demo
namespace: devops
resourceVersion: "192831"
uid: 4f4c7c5a-53ca-45f7-94ad-63e546cfcc62
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: pod-sa-demo
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-vxrcd
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
nodeName: kk-node01
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: sa-demo
serviceAccountName: sa-demo
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- name: kube-api-access-vxrcd
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
从上面可以看到 Service Account Token 被挂载到 Pod 中了。
$ kubectl exec -it -n devops pod-sa-demo -- /bin/sh
# ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token
其中 ca,crt 就是用来访问 API Server 的。
如果一个 Pod 在定义时没有指定 spec.serviceAccountName 属性,则系统会自动为其赋值为 default,即大家都使用同一个 Namespace 下的默认 Service Account。
Subjects 的 kind 类型除了 User,ServiceAccount 之外,还有一个 Group,就是一组用户的意思。如果你为 Kubernetes 配置了外部认证服务的话,这个用户组就由外部认证服务提供。而对于 Kubernetes 内置用户 ServiceAccount 来说,其也有用户和用户组的概念,其中对于一个 ServiceAccount,其在 Kubernetes 中对应的用户是:
system:serviceaccount:<ServiceAccount名字>
而对于其用户组是:
system:serviceaccounts:<Namespace名字>
比如我们定义下面这个 RoleBinding:
subjects:
- kind: Group
name: system:serviceaccounts:devops
apiGroup: rbac.authorization.k8s.io
这就意味着 Role 这个角色的权限规则作用与 devops 的 namespace 中的所有 ServiceAccount。
再比如:
subjects:
- kind: Group
name: system:serviceaccounts
apiGroup: rbac.authorization.k8s.io
这就意味着 Role 这个角色规则作用与整个集群的所有 ServiceAccount。
kubernetes 已经内置了许多 ClusterRole,以 system:开头,可以用 kubectl get clusterrole 查看。
另外,Kubernetes 还提供了四个预先定义好的 ClusterRole 来供用户直接使用,它们是:
- cluster-admin:超管
- admin:普通管理权限
- edit:修改权限
- view:只读权限
我们在定义 RoleBinding 或 ClusterRolebinding 的时候可以直接使用。
最后
Kubernetes 的权限管理就介绍到这里,本章节主要介绍了认证、授权的大概流程以及在 Kubernetes 中是如何实现认证、授权的。学完本章,你可以掌握认证用户有哪些,有哪些认证策略,以及如何使用 RBAC 实现鉴权。