Kubernetes 系统提供了三种认证方式:CA 认证、Token 认证 和 Base 认证。安全功能是一把双刃剑,它保护系统不被攻击,但是也带来额外的性能损耗。集群内的各组件访问 API Server 时,由于它们与 API Server 同时处于同一局域网内,所以建议用非安全的方式访问 API Server 效率更高。
接下来对集群的双向认证配置和简单认证配置过程进行详细说明。
双向认证配置
双向认证方式是最为严格和安全的集群安全配置方式,主要配置流程如下:
生成根证书、API Server 服务端证书、服务端私钥、各个组件所用的客户端证书和客户端私钥。
修改 Kubernetes 各个服务进程的启动参数,启用双向认证模式。
详细的配置操作流程如下:
生成根证书
用 openssl 工具生成 CA 证书,请注意将其中 subject 等参数改为用户所需的数据,CN 的值通常是域名、主机名或 IP 地址。
下面分别从 Authentication、Authorization、Admission Control、Secret 和 Service Account 六个方面来说明集群的安全机制。
Authentication 认证
Kubernetes 对 API 调用使用 CA(Client Authentication)、Token 和 HTTP Base 方式实现用户认证。
使用 CA 认证的应用需包含一个 CA 认证机构给服务器端下发的根证书、服务端证书和私钥文件。因此 API Server 的三个参数“–client-ca-file”“–tls-cert-file”和“–tls-private-key-file”分别指向根证书文件、服务端证书文件和私钥文件。API Server 客户端应用的三个启动参数(例如 Kubectl 的三个参数 “certificate-authority”“client-certificate”和“client-key”),或客户端应用的 kubeconfig 配置文件中的配置项“certificate-authority”“client-certificate”和“client-key”分别指向根证书文件、客户端证书文件和私钥文件。
Kubernetes 的 CA 认证方式通过添加 API Server 的启动参数“–client-ca-file=SOMEFILE”实现,其中“SOMEFILE”为认证授权文件,该文件包含一个或多个证书颁发机构(CA Certificates Authorities)。
Token 认证方式通过添加 API Server 的启动参数“–token_auth_file=SOMEFILE”实现,其中“SOMEFILE”指的是 Token 文件。
在 Kubernetes 中,授权(Authorization)是认证(Authenticaiton)后的一个独立步骤,作用于 API Server 主要端口的所有 HTTP 访问。授权流程不作用于只读端口,在计划中只读端口在不久之后将被删除。授权流程通过访问策略比较请求上下文的属性(例如用户名、资源和 Namespace)。在通过 API 访问资源之前,必须通过访问策略进行校验。访问策略通过 API Server 的启动参数 –authorization_mode 配置,该参数包含如下三个值:
–authorization_mode=AlwaysDeny
–authorization_mode=AlwaysAllow
–authorization_mode=ABAC
其中,“AlwaysDeny”表示拒绝所有的请求,该配置一般用于测试;“AlwaysAllow”表示接受所有请求,如果集群不需要授权流程,则可以采用该策略;“ABAC”表示使用用户配置的授权策略去管理访问 API Server 的请求,ABAC(Attribute-Based Access Control)为基于属性的访问控制。
在 Kubernetes 中,一个 HTTP 请求包含如下 4 个能被授权进程识别的属性:
用户名(代表一个已经被认证的用户的字符型用户名);
是否是只读请求(REST 的 GET 操作是只读的);
被访问的是哪一类资源,例如访问 Pod 资源/api/v1/namespaces/defaults/pods;
被访问对象所属的 Namespace,如果这被访问的资源不支持 Namespace,则是空字符串。
如果选用 ABAC 模式,那么需要通过设置 API Server 的“–authorization_policy_file=SOME_FILENAME”参数来指定授权策略文件,其中“SOME_FILENAME”为授权策略文件。授权策略文件的每一行都是一个 JSON 对象,该 JSON 对象是一个 Map,这个 Map 内不包含 List 和 Map。每行都是一个“策略对象”。策略对象包含下面 4 个属性:
用户 bob 只能读取 Namespace "myNamespace" 中的资源 Pods:{"user":"bob","resource":"pods","readonly":true,"ns":"myNamespace"}
Admission Control 准入控制
Admission Control 是用于拦截所有经过认证和鉴权后的访问 API Server 请求的可插入代码(或插件)。这些可插入代码运行于 API Server 进程中,在被调用前必须被编译成二进制文件。在请求被 API Server 接收前,每个 Admission Control 插件按配置顺序执行。如果其中的任意一个插件拒绝该请求,就意味着这个请求被 API Server 拒绝,同时 API Server 反馈一个错误信息给请求发起方。
在某些情况下,Admission Control 插件会使用系统配置的默认值取改变进入集群对象的内容。此外,Admission Control 插件可能会改变请求处理所使用的配额,比如增加请求处理的资源配额。
通过配置 API Server 的启动参数“admission_control”,在该参数中加入需要的 Admission Control 插件列表,各插件的名称之间用逗号隔开。例如:
Kubernetes 在 Pod 创建时,如果该 Pod 指定了 Service Account,那么为该 Pod 自动添加包含凭证信息的 Secrets,用于访问 API Server 和下载 Image。该功能可以通过 Admission Control 添加或失效,然而如果需要以安全的方式去访问 API Server,则建议开启该功能。
Service Account 是多个 Secret 的集合。它包含两类 Secret:一类为普通 Secret,用于访问 API Server,也被称为 Service Account Secret;另一类为 imagePullSecret,用于下载容器镜像。如果镜像库运行在 Insecure 模式下,则该 Service Account 可以不包含 imagePullSecret。在下面的例子中创建了一个名为 build-robot 的 Service Account,并查询该 Service Account 的信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cat > serviceaccount.yaml << EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: myserviceaccount
secrets:
- name: mysecret
kind: Secret
apiVersion: v1
- name: mysecret1
kind: Secret
apiVersion: v1
imagePullSecrets:
- name: mysecret2
EOF
$ kubectl create -f serviceaccount.yaml
$ kubectl get serviceaccounts build-robot -o yaml
在 Pod 的创建过程中指定“spec.serviceAccountName”的值为相应的 Service Account 的名称:
在 Kubernetes 系统中,Pod 在访问其他 Pod 的 Service 时,可以通过两种服务发现方式完成,即环境变量和 DNS 方式。但是使用环境变量是有限制条件的,即 Service 必须在 Pod 之前被创建出来,然后系统才能在新建的 Pod 中自动设置与 Service 相关的环境变量。DNS 则没有这个限制,其通过全局的 DNS 服务器来完成服务的注册与发现。
例如,我们要给已创建的 Pod “redis-master-bobr0”添加一个标签 role=backend:
1
$ kubectl label pod redis-master-bobr0 role=backend
删除一个 Label,只需在命令后最后指定 Label 的 key 名并与一个减号相连即可:
1
$ kubectl label pod redis-master-bobr0 role-
修改一个 Label 的值,需要加上 –overwrite 参数:
1
$ kubectl lable pod redis-master-bobr0 role=master --overwrite
将 Pod 调度到指定的 Node
Kubernetes 的 Scheduler 服务(kube-scheduler 进程)负责实现 Pod 的调度,整个调度过程通过一系列复杂的算法最终为每个 Pod 计算出一个最佳的目标节点,这一过程是自动完成的,我们无法知道 Pod 最终会被调度到哪个节点上。有时我们可能需要将 Pod 调度到一个指定的 Node 上,此时,我们可以通过 Node 的标签(Label)和 Pod 的 nodeSelector 属性相匹配,来达到上述目的。