Kubernetes 安全机制

Kubernetes 通过一系列机制来实现集群的安全控制,其中包括 API Server 的认证授权、准入控制机制及保护敏感信息的 Secret 机制等。集群的安全性必须考虑如下几个目标:

  1. 保证容器与其所在的宿主机的隔离;
  2. 限制容器给基础设施及其他容器带来消极影响的能力;
  3. 最小权限原则————合理限制所有组件的权限,确保组件只执行它被授权的行为,通过限制单个组件的能力来限制它所能到达的权限范围;
  4. 明确组件间边界的划分;
  5. 划分普通用户和管理员的角色;
  6. 在必要的时候允许将管理员权限赋给普通用户;
  7. 允许拥有“Secret”数据(Keys、Certs、Passwords)的应用在集群中运行。

下面分别从 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 文件。

1
2
3
4
5
6
7
8
9
例如 Token 文件内容为:
lkjqweroiuuou,Thomas,8x7d1kklzseertyywx
用 CURL 去访问该 API Server:
curl $APISERVER/api --header "Authorization: Bearer lkjqweroiuuou" --insecure
{
"version" : [
"v1"
]
}

基本认证方式是通过添加 API Server 的启动参数“–basic_auth_file=SOMEFILE”实现的,其中“SOMEFILE”指的是用于存储用户和密码信息的基本认证文件。

当使用基本认证方式从 HTTP 客户端访问 API Server 时,HTTP 请求头中的 Authorization 域必须包含“Basic BASE64ENCODEDUSER:PASSWORD”的值。

1
2
3
4
5
6
7
tmp=`base64 "dingmingk:passwd"`
curl $APISERVER/api --header "Authorization: Basic $tmp" --insecure
{
"version":[
"v1"
]
}

Authorization 授权

在 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 个属性:

  • user(用户名),为字符串类型,该字符串类型的用户名来源于 Token 文件或基本认证文件中的用户名字段的值;
  • readonly(只读标识),为布尔类型,当它的值为 true 时,表明该策略允许 GET 请求通过;
  • resource(资源),为字符串类型,来自于 URL 的资源,例如“Pods”;
  • namespace(命名空间),为字符串类型,表明该策略允许访问某个 Namespace 的资源。

没被设置的属性,将被等同于根据值的类型设置成零值(例如为字符串类型属性设置一个空字符串;为布尔值属性设置 false;为数值类型属性设置 0)。

授权策略文件中的策略对象的一个未设置属性,表示匹配 HTTP 请求中该属性的任何值。对请求的 4 个属性值和授权策略文件中的所有策略对象逐个匹配,如果至少有一个策略对象被匹配上,则该请求将被授权通过。

例如:

1
2
3
4
允许用户 alice 做任何事情:{"user":"alice"}。
用户 Kubelet 指定读取资源 Pods:{"user":"kubelet","resource":"pods","readonly":true}。
用户 Kubelet 能读和写资源 events:{"user":"kubelet","resource":"events"}
用户 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 插件列表,各插件的名称之间用逗号隔开。例如:

1
--admission_control=NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota

Admission Control 的插件列表如下表所示:

名称 说明
AlwaysAdmit 允许所有请求通过
AlwaysDeny 拒绝所有请求,一般用于测试
DenyExecOnPrivileged 拦截所有带有 SecurityContext 属性的 Pod 的请求,拒绝在一个特权容器中执行命令
ServiceAccount 配合 Service Account Controller 使用,为设定了 Service Account 的 Pod 自动管理 Secret,使得 Pod 能够使用相应的 Secret 下载 Image 和访问 API Server
SecurityContextDeny 不允许带有 SecurityContext 属性的 Pod 存在,SecurityContext 属性用于创建特权容器
ResourceQuota 在 Namespace 中做资源配额限制
LimitRanger 限制 Namespace 中的 Pod 和 Container 的 CPU 和 内存配额
NamespaceExists 读区请求中的 Namespace 属性,如果该 Namespace 不存在,则拒绝该请求
NamespaceAutoProvision(deprecated) 读取请求中的 Namespace 属性,如果该 Namespace 不存在,则尝试创建该 Namespace
NamespaceLifecycle 该插件限制访问处于中止状态的 Namespace,禁止在该 Namespace 中创建新的内容。当 NamespaceLifecycle 和 NamespaceExists 能够合并成一个插件后,NamespaceAutoProvision 就会变成 deprecated

Secret 私密凭据

Secret 的主要作用是保管私密数据,比如密码、OAuth Tokens、SSH Keys 等信息。将这些私密信息放在 Secret 对象中比直接放在 Pod 或 Docker Image 中更安全,也更便于使用。

Kubernetes 在 Pod 创建时,如果该 Pod 指定了 Service Account,那么为该 Pod 自动添加包含凭证信息的 Secrets,用于访问 API Server 和下载 Image。该功能可以通过 Admission Control 添加或失效,然而如果需要以安全的方式去访问 API Server,则建议开启该功能。

下面的例子用于创建一个 Secret:

1
2
3
4
5
6
7
8
9
10
11
$ kubectl namespace myspace
$ cat <<EOF > secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
password: dmFsdWUtMgOK
username: dmFsdWUtMQOK
$ kubectl create -f secrets.yaml

在上面的例子中,data域的各子域的值必须为 base64 编码值,其中 password 域和 username 域 base64 编码前的值分别为“value-1” 和“value-2”。

一旦 Secret 被创建,则可以通过下面的三种方式使用它:

  1. 在创建 Pod 时,通过为 Pod 指定 Service Account 来自动使用该 Secret;
  2. 通过挂载该 Secret 到 Pod 来使用它;
  3. 在创建 Pod 时,指定 Pod 的 spc.ImagePullSecrets 来引用它。

将一个 Secret 通过挂载的方式添加到 Pod 的 Volume 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: myPod
namespace: myns
spec:
containers:
name: mycontainer
image: redis
volumeMounts:
- name: foo
mountPath: /etc/foo
readOnly: true
volumes:
- name: foo
secret:
secretNmae: mysecret

手动使用 imagePullSecret:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ docker login localhost:1180
用 base64 编码 dockercfg 的内容
$ cat ~/.dockercfg | base64
将上一步命令的输出结果作为 Secret 的“data.dockercfg” 域的内容,由此来创建一个 Secret
$ cat > image-pull-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
name: myregistrykey
data:
.dockercfg: xxxxxxxxxxxxxxxxxxxxx
type: kubernetes.io/dockercfg
EOF
$ kubectl create -f image-pull-secret.yaml
在创建 Pod 时,引用该 Secret
$ cat > pods.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
name: mypod2
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: myregistrykey
EOF
$ kubectl create -f pods.yaml

Pod 创建时会验证所挂载的 Secret 是否真的指向一个 Secret 对象,因此 Secret 必须在任何引用它的 Pod 之前被创建。Secret 对象属于 Namespace,它们只能被同一个 NameSpace 中的 Pod 所引用。

Secret 包含三种类型:Opaque、ServiceAccount 和 Dockercfg。前面举例介绍了如何创建 Opaque 和 Dockercfg 类型的 Secret。下面的例子为创建一个 Service Account Secret:

1
2
3
4
5
6
7
apiVersion: v1
kind: Secret
metadata:
name: mysecret
annotations:
kubernetes.io/service-account.name: myserviceaccount
type: kubernetes.io/service-account-token

Service Account

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 的名称:

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mycontainer
image: nginx
serviceAccountName: myserviceaccount