在 k8s 中,所有资源的访问和变更都是围绕 APIServer 展开的。比如说 kubectl 命令、客户端 HTTP RESTFUL 请求,都是去 call APIServer 的 API 进行的,本文就重点解读 k8s 为了集群安全,都做了些什么。
Kubernetes 官方文档给出了上面这张图,描述了用户在访问或变更资源的之前,需要经过 APIServer 的认证机制、授权机制以及准入控制机制。这三个机制可以这样理解,先检查是否合法用户,再检查该请求的行为是否有权限,最后做进一步的验证或添加默认参数。
k8s 中有两种用户,一种是内置“用户” ServiceAccount,另一种我称之为自然人。
所谓自然人就是指区别于 pod 等资源概念的“人”,可以理解成实际操作 "kubectl" 命令的人。admin 可以分发私钥,但自然人可以储存类似 KeyStone 甚至包含账号密码的文件,所以 k8s 中没有对自然人以 API 对象描述之。
在典型的 k8s 集群中,API 通常服务在 443 端口,APIServer 提供自签名证书。当你使用 kube-up.sh
创建集群用户时,证书会自动在 $USER/.kube/config
中创建出来,而后续用 kubectl
命令访问 APIServer 时,都是用这个证书。
与之相反,k8s 中以 API 对象的形式描述和管理 ServiceAccount。它们被绑定在某个具体的 namespace 中,可以由 APIServer 自动创建出来或手动 call k8s API。
k8s 中的认证机制,是在用户访问 APIServer 的第一步。通常是一个完整的 HTTP 请求打过来,但是这一步往往只检测请求头或客户端证书。
认证机制目前有客户端证书、bearer tokens、authenticating proxy、HTTP basic auth 这几种模式。使用方式通常有以下几种:
X509 Client Certs: 客户端证书模式需要在 kubectl
命令中加入 --client-ca-file=<SOMEFILE>
参数,指明证书所在位置。
Static Token File: --token-auth-file=<SOMEFILE>
参数指明 bearer tokens
所在位置。
bearer tokens: 在 HTTP 请求头中加入 Authorization:Bearer<TOKEN>
。
Bootstrap Tokens: 与 bearer tokens
一致,但 TOKEN 格式为 [a-z0-9]{6}.[a-z0-9]{16}
。该方式称为 dynamically-managed Bearer token,以 secret
的方式保存在 kube-systemnamespace
中,可以被动态的创建和管理。同时,启用这种方式还需要在 APIServer 中打开 --enable-bootstrap-token-auth
,这种方式还处于 alpha 阶段。
Static Password File: 以参数 --basic-auth-file=<SOMEFILE>
指明 basic auth file 的位置。这个 basic auth file 以 csv 文件的形式存在,里面至少包含三个信息:password、username、user id,同时该模式在使用时需要在请求头中加入 Authorization:BasicBASE64ENCODED(USER:PASSWORD)
。
Service Account Tokens: 该方式通常被 pod 所使用,在 PodSpec
中指明 ServiceAccount 来访问 ApiServer。
除了以上列出来的几种方式外,还有一些比较特殊的访问方式,这里不再详细解读。
当用户通过认证后,k8s 的授权机制将对用户的行为等进行授权检查。换句话说,就是对这个请求本身,是否对某资源、某 namespace、某操作有权限限制。
授权机制目前有 4 种模式:RBAC、ABAC、Node、Webhook。下面对这 4 种模式分别做分析。
Role-based access control (RBAC) 是基于角色的权限访问控制,通常是对于“内置用户”而言的。该模式是在 k8s v1.6 开发出来的。若要开启该模式,需要在 APIServer 启动时,设置参数 --authorization-mode=RBAC
。
RBAC 所使用的 API Group 是 rbac.authorization.k8s.io/v1beta1
,直到 Kubernetes v1.8 后,RBAC 模块达到稳定水平,所使用的 API Group 为 rbac.authorization.k8s.io/v1
。
所谓基于角色的权限访问控制,就是对某个用户赋予某个角色,而这个角色通常决定了对哪些资源拥有怎样的权限。
首先来看看这个 “内置用户”,在大多时候我们都不使用 “自然人” 这个功能,而是使用 ServiceAccount,再对其他资源授予某个 ServiceAccount,就使得其能够以 “内置用户” 的身份去访问 APIServer。
创建一个 ServiceAccount 很简单,只需要指定其所在 namespace 和 name 即可。举个例子:
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: hdls
name: hdls-sa
RBAC 中最重要的概念就是 Role
和 RoleBinding
。 Role
定义了一组对 Kubernetes API 对象的操作权限,而 RoleBinding
则定义的是具体的 ServiceAccount 和 Role 的对应关系。
举个 Role 的例子如下:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: hdls
name: hdls-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
其中: namespace: 在这里仅限于逻辑上的“隔离”,并不会提供任何实际的隔离或者多租户能力; rules:定义的是权限规则,允许“被作用者”,对 hdls 下面的 Pod 对象,进行 GET 和 LIST 操作; apiGroups:为 "" 代表 core API Group; resources:指的是资源类型,对此还可以进行详细的划分,指定可以操作的资源的名字,比如:
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["my-config"]
verbs: ["get"]
verbs: 指的是具体的操作,当前 Kubernetes(v1.11)里能够对 API 对象进行的所有操作有 "get", "list", "watch", "create", "update", "patch", "delete"。
再看 RoleBinding 的例子:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: hdls-rolebinding
namespace: hdls
subjects:
- kind: ServiceAccount
name: hdls-sa
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: hdls-role
apiGroup: rbac.authorization.k8s.io
可以看到,这个 RoleBinding 对象里定义了一个 subjects 字段,即“被作用者”。它的类型是 ServiceAccount,就是上面创建的 sa。这个 subjects 还可以是 User 和 Group,User 是指 k8s 里的用户,而 Group 是指 ServiceAccounts。
roleRef
字段是用来直接通过名字,引用我们前面定义的 Role 对象(hdls-role),从而定义了 Subject 和 Role 之间的绑定关系。
此时,我们再用 kubectlgetsa-n hdls-o yaml
命令查看之前的 ServiceAccount
,就可以看到 ServiceAccount.secret
,这是因为 k8s 会为一个 ServiceAccount
自动创建并分配一个 Secret
对象,而这个 Secret
就是用来跟 APIServer 进行交互的授权文件: Token
。 Token
文件的内容一般是证书或者密码,以一个 Secret
对象的方式保存在 etcd 当中。
这个时候,我们在我们的 Pod 的 YAML 文件中定义字段 .spec.serviceAccountName
为上面的 ServiceAccountname
即可声明使用。
如果一个 Pod 没有声明 serviceAccountName
,Kubernetes 会自动在它的 Namespace 下创建一个名叫 default
的默认 ServiceAccount
,然后分配给这个 Pod。然而这个默认 ServiceAccount 并没有关联任何 Role。也就是说,此时它有访问 APIServer 的绝大多数权限。
需要注意的是 Role 和 RoleBinding 对象都是 Namespaced 对象,它们只对自己的 Namespace 内的资源有效。
而某个 Role 需要对于非 Namespaced 对象(比如:Node),或者想要作用于所有的 Namespace 的时候,我们需要使用 ClusterRole 和 ClusterRoleBinding 去做授权。
这两个 API 对象的用法跟 Role 和 RoleBinding 完全一样。只不过,它们的定义里,没有了 Namespace 字段。
值得一提的是,Kubernetes 已经内置了很多个为系统保留的 ClusterRole
,它们的名字都以 system:
开头。一般来说,这些系统级别的 ClusterRole
,是绑定给 Kubernetes 系统组件对应的 ServiceAccount
使用的。
除此之外,Kubernetes 还提供了四个内置的 ClusterRole
来供用户直接使用:
cluster-admin:整个集群的最高权限。如果在 ClusterRoleBinding
中使用,意味着在这个集群中的所有 namespace 中的所有资源都拥有最高权限,为所欲为;如果在 RoleBinding
中使用,即在某个 namespace 中为所欲为。
admin:管理员权限。如果在 RoleBinding
中使用,意味着在某个 namespace 中,对大部分资源拥有读写权限,包括创建 Role 和 RoleBinding 的权限,但没有对资源 quota 和 namespace 本身的写权限。
edit:写权限。在某个 namespace 中,拥有对大部分资源的读写权限,但没有对 Role 和 RoleBinding 的读写权限。
view:读权限。在某个 namespace 中,仅拥有对大部分资源的读权限,没有对 Role 和 RoleBinding 的读权限,也没有对 seccrets 的读权限。
在 Kubernetes v1.9 之后, ClusterRole
有一种新的定义方法,就是使用 aggregationRule
将多个 ClusterRole
合成一个新的 ClusterRole
。
首先看个 k8s 官网的例子:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: []
其中 rules
字段不必定义,会被 controller manager
自动填充。
可以看出 aggregationRule
就是将所有满足 label
条件的 ClusterRole
的合成一个 ClusterRole
,而这个新的 ClusterRole
权限为其他总和。
相对于 User 而言,k8s 还拥有“用户组”(Group)的概念,也就是一组“用户”的意思。而对于“内置用户” ServiceAccount 来说,“用户组”的概念也同样适用。
实际上,一个 ServiceAccount,在 Kubernetes 里对应的“用户”的名字是: system:serviceaccount:<ServiceAccount名字>
;而它对应的内置“用户组”的名字,就是 system:serviceaccounts:<Namespace名字>
。
对于 Group 的运用,我们举个例子,在 RoleBinding 里这样定义 subjects:
subjects:
- kind: Group
name: system:serviceaccounts:hdls
apiGroup: rbac.authorization.k8s.io
这就意味着这个 Role 的权限规则,作用于 hdls 里的所有 ServiceAccount。
而如果 Group 不指定 Namespace,即直接定义为 system:serviceaccounts
,意味着作用于整个系统里的所有 ServiceAccount。
Attribute-based access control (ABAC) 是基于属性的权限访问控制。若要开启该模式,需要在 APIServer 启动时,开启 --authorization-policy-file=<SOME_FILENAME>
和 --authorization-mode=ABAC
两个参数。
其 policy 文件用来指定权限规则,必须满足每行都是一个 json 对象的格式。可以指定 user 或 group 为某个特定的对象,并描述其拥有的权限。
与 Yaml 文件一致,必须描述的属性有 apiVersion、kind、spec,而 spec 里描述了具体的用户、资源和行为。看个例子:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "bob", "namespace": "projectCaribou", "resource": "pods", "readonly": true}}
这就描述了用户 bob 只有在 namespace projectCaribou 下对 pod 的读权限。类似的,这个 User 可以是某个人,也可以是 kubelet 或者某个 ServiceAccount,这里 ServiceAccount 需要写全,比如: system:serviceaccount:kube-system:default
。
如果是描述某个 namespace 下的所有人,需要用到 group,比如:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group": "system:serviceaccounts:default", "readonly": true, "resource": "pods"}}
Node 授权机制是一种特殊的模式,是 kubelet
发起的请求授权。开启该模式,需要开启参数 --authorization-mode=Node
。
通过启动 --enable-admission-plugins=...,NodeRestriction,...
,来限制 kubelet 访问 node,endpoint、pod、service以及secret、configmap、PV 和 PVC 等相关的资源。
Webhook 模式是一种 HTTP 回调模式,是一种通过 HTTP POST
方式实现的简单事件通知。该模式需要 APIServer 配置参数 –authorization-webhook-config-file=<SOME_FILENAME>
,HTTP 配置文件的格式跟 kubeconfig 的格式类似。
# Kubernetes API version
apiVersion: v1
# kind of the API object
kind: Config
# clusters refers to the remote service.
clusters:
- name: name-of-remote-authz-service
cluster:
# CA for verifying the remote service.
certificate-authority: /path/to/ca.pem
# URL of remote service to query. Must use 'https'. May not include parameters.
server: https://authz.example.com/authorize
# users refers to the API Server's webhook configuration.
users:
- name: name-of-api-server
user:
client-certificate: /path/to/cert.pem # cert for the webhook plugin to use
client-key: /path/to/key.pem # key matching the cert
# kubeconfig files require a context. Provide one for the API Server.
current-context: webhook
contexts:
- context:
cluster: name-of-remote-authz-service
user: name-of-api-server
name: webhook
其中,Cluster 指需要回调的地方的客户端,指定其访问证书和 URL;user 指回调处访问的身份,指明其所需证书和 key;contexts 指回调的内容。
在一个请求通过了认证机制和授权认证后,需要经过最后一层筛查,即准入控制。这个准入控制模块的代码通常在 APIServer 中,并被编译到二进制文件中被执行。这一层安全检查的意义在于,检查该请求是否达到系统的门槛,即是否满足系统的默认设置,并添加默认参数。
准入控制以插件的形式存在,开启的方式为:
kube-apiserver --enable-admission-plugins=NamespaceLifecycle,LimitRanger ...
关闭的方式为:
kube-apiserver --disable-admission-plugins=PodNodeSelector,AlwaysDeny ...
常用的准入控制插件有:
PersistentVolumeClaim
创建默认的 PV;node.kubernetes.io/not-ready:NoExecute
和 node.alpha.kubernetes.io/unreachable:NoExecute
没有容忍,为其创建默认的 5 分钟容忍 notready:NoExecute
和 unreachable:NoExecute
;priorityClassName
来决定优先级;以上只列举了部分,详情请移步 Kubernetes 官方文档。
官方建议:
--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota
--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota
--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds
--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota