深入浅出 Kubernetes 安全机制

1,983 阅读11分钟

《深入浅出 Kubernetes 安全机制》最早发布在 blog.hdls.me/15432142284…

在 Kubernetes 中,所有资源的访问和变更都是围绕 APIServer 展开的。比如说 kubectl 命令、客户端 HTTP RESTFUL 请求,都是去 call APIServer 的 API 进行的,本文就重点解读 k8s 为了集群安全,都做了些什么。

首先,Kubernetes 官方文档给出了上面这张图。描述了用户在访问或变更资源的之前,需要经过 APIServer 的认证机制、授权机制以及准入控制机制。这三个机制可以这样理解,先检查是否合法用户,再检查该请求的行为是否有权限,最后做进一步的验证或添加默认参数。

用户

Kubernetes 中有两种用户,一种是内置“用户” ServiceAccount,另一种我称之为自然人。

所谓自然人就是指区别于 pod 等资源概念的“人”,可以理解成实际操作 "kubectl" 命令的人。admin 可以分发私钥,但自然人可以储存类似 KeyStone 甚至包含账号密码的文件,所以 k8s 中没有对自然人以 API 对象描述之。

在典型的 Kubernetes 集群中,API 通常服务在 443 端口,APIServer 提供自签名证书。当你使用 kube-up.sh 创建集群用户时,证书会自动在 $USER/.kube/config 中创建出来,而后续用 kubectl 命令访问 APIServer 时,都是用这个证书。

与之相反,k8s 中以 API 对象的形式描述和管理 ServiceAccount。它们被绑定在某个具体的 namespace 中,可以由 APIServer 自动创建出来或手动 call k8s API。

认证机制(Authentication)

k8s 中的认证机制,是在用户访问 APIServer 的第一步。通常是一个完整的 HTTP 请求打过来,但是这一步往往只检测请求头或客户端证书。

认证机制目前有客户端证书、bearer tokens、authenticating proxy、HTTP basic auth 这几种模式。使用方式通常有以下几种:

  1. X509 Client Certs: 客户端证书模式需要在 kubectl 命令中加入 --client-ca-file=<SOMEFILE> 参数,指明证书所在位置。

  2. Static Token File: --token-auth-file=<SOMEFILE> 参数指明 bearer tokens 所在位置。

  3. bearer tokens: 在 HTTP 请求头中加入 Authorization: Bearer <TOKEN>

  4. Bootstrap Tokens: 与 bearer tokens 一致,但 TOKEN 格式为 [a-z0-9]{6}.[a-z0-9]{16}。该方式称为 dynamically-managed Bearer token,以 secret 的方式保存在 kube-system namespace 中,可以被动态的创建和管理。同时,启用这种方式还需要在 APIServer 中打开 --enable-bootstrap-token-auth ,这种方式还处于 alpha 阶段。

  5. Static Password File: 以参数 --basic-auth-file=<SOMEFILE> 指明 basic auth file 的位置。这个 basic auth file 以 csv 文件的形式存在,里面至少包含三个信息:password、username、user id,同时该模式在使用时需要在请求头中加入 Authorization: Basic BASE64ENCODED(USER:PASSWORD)

  6. Service Account Tokens: 该方式通常被 pod 所使用,在 PodSpec 中指明 ServiceAccount 来访问 ApiServer。

除了以上列出来的几种方式外,还有一些比较特殊的访问方式,这里不再详细解读。

授权机制(Authorization)

当用户通过认证后,k8s 的授权机制将对用户的行为等进行授权检查。换句话说,就是对这个请求本身,是否对某资源、某 namespace、某操作有权限限制。

授权机制目前有 4 种模式:RBAC、ABAC、Node、Webhook。下面对这 4 种模式分别做分析。

RBAC

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,再对其他资源授予某个 ServiceAccount,就使得其能够以 “内置用户” 的身份去访问 APIServer。

创建一个 ServiceAccount 很简单,只需要指定其所在 namespace 和 name 即可。举个例子:

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: hdls
  name: hdls-sa

Role & Rolebinding

RBAC 中最重要的概念就是 RoleRoleBindingRole 定义了一组对 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 之间的绑定关系。

此时,我们再用 kubectl get sa -n hdls -o yaml 命令查看之前的 ServiceAccount,就可以看到 ServiceAccount.secret,这是因为 k8s 会为一个 ServiceAccount 自动创建并分配一个 Secret 对象,而这个 Secret 就是用来跟 APIServer 进行交互的授权文件: TokenToken 文件的内容一般是证书或者密码,以一个 Secret 对象的方式保存在 etcd 当中。

这个时候,我们在我们的 Pod 的 YAML 文件中定义字段 .spec.serviceAccountName 为上面的 ServiceAccount name 即可声明使用。

如果一个 Pod 没有声明 serviceAccountName,Kubernetes 会自动在它的 Namespace 下创建一个名叫 default 的默认 ServiceAccount,然后分配给这个 Pod。然而这个默认 ServiceAccount 并没有关联任何 Role。也就是说,此时它有访问 APIServer 的绝大多数权限。

ClusterRole & ClusterRoleBinding

需要注意的是 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 的读权限。

Aggregated ClusterRoles

在 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 权限为其他总和。

Group

相对于 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。

ABAC

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

Node 授权机制是一种特殊的模式,是 kubelet 发起的请求授权。开启该模式,需要开启参数 --authorization-mode=Node

通过启动 --enable-admission-plugins=...,NodeRestriction,...,来限制 kubelet 访问 node,endpoint、pod、service以及secret、configmap、PV 和 PVC 等相关的资源。

Webhook

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 指回调的内容。

准入控制(Admission Controllers)

在一个请求通过了认证机制和授权认证后,需要经过最后一层筛查,即准入控制。这个准入控制模块的代码通常在 APIServer 中,并被编译到二进制文件中被执行。这一层安全检查的意义在于,检查该请求是否达到系统的门槛,即是否满足系统的默认设置,并添加默认参数。

准入控制以插件的形式存在,开启的方式为: kube-apiserver --enable-admission-plugins=NamespaceLifecycle,LimitRanger ...

关闭的方式为: kube-apiserver --disable-admission-plugins=PodNodeSelector,AlwaysDeny ...

常用的准入控制插件有:

  • AlwaysAdmit:允许所有请求通过,被官方反对,因为没有实际意义;
  • AlwaysPullImages:将每个 pod 的 image pull policy 改为 always,在多租户的集群被使用;
  • AlwaysDeny:禁止所有请求通过,被官方反对,因为没有实际意义;
  • DefaultStorageClass:为每个 PersistentVolumeClaim 创建默认的 PV;
  • DefaultTolerationSeconds:如果 pod 对污点 node.kubernetes.io/not-ready:NoExecutenode.alpha.kubernetes.io/unreachable:NoExecute 没有容忍,为其创建默认的 5 分钟容忍 notready:NoExecuteunreachable:NoExecute
  • LimitRanger:确保每个请求都没有超过其 namespace 下的 LimitRange,如果在 Deployment 中使用了 LimitRange 对象,该准入控制插件必须开启;
  • NamespaceAutoProvision:检查请求中对应的 namespace 是否存在,若不存在自动创建;
  • NamespaceExists:检查请求中对应的 namespace 是否存在,若不存在拒绝该请求;
  • NamespaceLifecycle:保证被删除的 namespace 中不会创建新的资源;
  • NodeRestriction:不允许 kubelet 修改 Node 和 Pod 对象;
  • PodNodeSelector:通过读取 namespace 的注解和全局配置,来控制某 namespace 下哪些 label 选择器可被使用;
  • PodPreset:满足预先设置的标准的 pod 不允许被创建;
  • Priority:通过 priorityClassName 来决定优先级;
  • ResourceQuota:保证 namespace 下的资源配额;
  • ServiceAccount:保证 ServiceAccount 的自动创建,如果用到 ServiceAccount,建议开启;

以上只列举了部分,详情请移步 Kubernetes 官方文档。

官方建议:

  • 版本 > v1.10:
--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota
  • v1.9
--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota
  • v1.6 - v1.8
--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds
  • v1.4 - v1.5
--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota