一、前言
安全是K8S重要的特性,在K8S初级入门系列之四-Namespace/ConfigMap/Secret章节,我们已经已经了解了Namespace,Secret与安全相关的知识。本篇将梳理K8S在安全方面的策略。主要包括两个方面,API安全访问策略以及Pod安全策略。
二、用户/用户组
在介绍安全前,我们先了解下用户和用户组的概念。
1、用户
在K8S中,用户分为两种:
- 真实的用户,即User,如K8S的管理员,开发者等。
- Pod的账号,即Service Account,是给运行在Pod里面的进程提供必要的身份证明,通过其来限制Pod的访问权限。
这里重点看下Service Account,每个命名空间(namespace)都有一个默认的Service Account。如果Pod不指定ServiceAccount,则使用该空间中默认的。
css
[root@k8s-master ~]# kubectl get sa
NAME SECRETS AGE
default 1 203d
(1)自定义ServiceAccount
当然,我们也可以自定义一个ServiceAccount,其yaml如下:
css
[root@k8s-master yaml]# cat my-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-servicesaccount
namespace: default
创建完成后,查看详情
css
[root@k8s-master yaml]# kubectl describe sa my-servicesaccount
Name: my-servicesaccount
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: my-servicesaccount-token-cz8kp
Tokens: my-servicesaccount-token-cz8kp
Events: <none>
可以看到 mountable secret和tokens都关联了一个名为my-servicesaccount-token-cz8kp的secret,我们查看其secret的详情
css
[root@k8s-master yaml]# kubectl describe secret my-servicesaccount-token-cz8kp
Name: my-servicesaccount-token-cz8kp
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: my-servicesaccount
kubernetes.io/service-account.uid: 1f493157-13e7-4d43-9f0b-b8779dfe84ae
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1099 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6ImpoSnJ...
其data包含ca.crt,namespace,token密钥三部分,其中ca.crt即颁发的CA证书,namespace即命名空间,token文件中存放的密钥。这三部分的用途我们待会讲到。
(2)将ServiceAccount分配给Pod
创建Pod,将上面的serviceAccount分配给Pod,其yaml内容如下:
css
[root@k8s-master yaml]# cat pod-read.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-read
spec:
serviceAccount: my-servicesaccount
containers:
- name: netshoot
image: nicolaka/netshoot
imagePullPolicy: IfNotPresent
command: ["sleep","3600"]
该Pod中定义serviceAccount属性,并设置为刚创建的my-servicesaccount。我们进入该pod,查看下/var/run/secrets/kubernetes.io/serviceaccount目录。
css
[root@k8s-master ~]# kubectl exec -it pod-read sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
~ # cd /var/run/secrets/kubernetes.io/serviceaccount
/run/secrets/kubernetes.io/serviceaccount # ls
ca.crt namespace token
/run/secrets/kubernetes.io/serviceaccount # cat token
eyJhbGciOiJSUzI1NiIsImtpZCI6ImpoSnJ
该目录下,包含了my-servicesaccount的secret的三个文件。实际上,ServiceAcccount分配给Pod,就是将serviceAccount的secret作为volume挂载到pod的的固定目录下(即/var/run/secrets/kubernetes.io/serviceaccount ),后续Pod将通过这三个文件协同完成身份验证。
2、用户组
多个用户可以属于一个或者多个用户组,用户组可以一次给多个用户赋予权限。K8S系统内置了一些组,如:
- system:unauthenticated组用于所有认证插件都不会认证客户端身份的请求。
- system:authenticated组会自动分配给一个成功通过认证的用户。
- system:serviceaccounts组包含所有在系统中的 ServiceAccount
- system:serviceaccounts:<namespace>组包含了所有在特定命名空间中的ServiceAccount。
三、API Server安全访问策略
在K8S初级入门系列之一-概述章节的K8S架构中,我们形象的比喻过,API Server就像是办事大厅,对外对内提供统一的访问接口,所以API Server访问安全策略至关重要。先来看下官方上的图。
当访问API Server时,需要经过三层关卡,分别是认证(Authentication),鉴权(Authorization)和准入控制(Admission Control)。
1、认证
认证就是识别用户的身份,对于Pod来说,在访问API Server时,携带其Service Account的token密钥(前面介绍的),由API Service进行认证,这种方式类似JWT token验证。过程如下:
(1)、Pod关联ServiceAccount,并挂载了ServiceAccount的secret。
(2)、通过HTTPS方式与API Server建立连接后,会用Pod里CA证书(文件名为ca.crt)验证API Server发来的证书,验证是否为CA证书签名的合法证书。
(3)、API Server收到Token后,采用自身私钥对Token进行合法性验证。
整个认证过程,需要用到上面介绍的var/run/secrets/kubernetes.io/serviceaccount下三个文件。我们来实验下,从Pod内部,通过curl访问Api Server的接口。其命令如下:
指向内部 API 服务器的主机名,一般为kubernetes.default.svc,也可以通过env查看
APISERVER=https://kubernetes.default.svc
服务账号令牌的路径
SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
读取 Pod 的名字空间
NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
读取服务账号的持有者令牌
TOKEN=$(cat ${SERVICEACCOUNT}/token)
引用内部证书机构(CA)
CACERT=${SERVICEACCOUNT}/ca.crt
使用令牌访问 API curl --cacert ${CACERT} -H "Authorization: Bearer $TOKEN" -s ${APISERVER}/api
进入pod内部,输入上述指令
css
[root@k8s-master ~]# kubectl exec -it pod-read sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
~ # APISERVER=https://kubernetes.default.svc
~ # SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
~ # NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
~ # TOKEN=$(cat ${SERVICEACCOUNT}/token)
~ # CACERT=${SERVICEACCOUNT}/ca.crt
~ # curl --cacert ${CACERT} -H "Authorization: Bearer $TOKEN" -s ${APISERVER}/api
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "192.168.16.4:6443"
}
]
}
可以看下,HTTPS携带相关认证信息,Api认证成功后,正确的返回了Api的相关信息。
2、鉴权
认证是对用户合法性的认证,但用户合法,不代表可以做任何操作,而鉴权是对调用的API是否合法进行鉴权,授予用户不同的访问权限。先看一个例子,我们进入上述Pod,访问下pod列表。
css
# curl --cacert ${CACERT} -H "Authorization: Bearer $TOKEN" -X GET ${APISERVER}/api/v1/namespaces/default/pods
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "default \"pods\" is forbidden: User \"system:serviceaccount:default:default\" cannot get resource \"default\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
"details": {
"name": "pods",
"kind": "default"
},
"code": 403
鉴权未通过被拒绝了,说明该Pod使用的ServiceAccount是没有访问该资源权限的。
下面我们来进行授权访问,授权的方式主要包括ABAC(基于属性授权),RBAC(基于角色授权),Webhook(外部REST服务对用户授权)等,其中RBAC是最主要,也是API Server默认的方式,我们来重点介绍,其模型如下:
- 资源权限,表示对何种对象,有什么的操作权限,对象包括核心资源,如Pod,Deployment,job等,也包括非资源端点,如"/healthz"等。
- Role(角色),是资源权限的集合。
- RoleBinding(角色绑定),将Role授予给ServiceAccount,User,Group等。
接下来,我们通过案例,实现对于Pod列表的访问。
(1)Role(角色)
首先创建一个Role的yaml文件,内容如下:
css
[root@k8s-master yaml]# cat pod-read-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 标明 core API 组
resources: ["pods"]
verbs: ["get", "watch", "list"]
在rules可以定义多组资源权限,每组包含三个属性:
- apiGoups,即资源对象所在的api组,由于pods是核心对象,所以为"",比如对象为jobs,那么该值就是batch。
- resources,资源对象组,可以是pods,deploymenets,jobs等等
- verbs,对资源对象的操作权限组,包括get,list,watch,create,delete等。
该Role命名为pod-reader,并申明对于Pod对象具备get,watch,list相关权限(注意,不具备删除,创建权限)。执行文件,创建role对象,查看状态。
css
[root@k8s-master yaml]# kubectl get role
NAME CREATED AT
pod-reader 2023-05-28T09:24:06Z
(2)RoleBinding
接下来创建RoleBinding,将上面的Role(pod-reader)与ServiceAccount(my-serviceaccount)绑定。其yaml内容如下:
css
[root@k8s-master yaml]# cat pod-reader-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-rolebinding
namespace: default
subjects:
# 这里可以指定多个主体,User,ServiceAccount,Group
- kind: ServiceAccount
name: my-servicesaccount
namespace: default
apiGroup: ""
roleRef:
# "roleRef" 指定与某 Role 绑定关系
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
rolebinding包含两个属性。
- subjects,即绑定的用户主体,可以是User,ServiceAccount,Group,能同时绑定多个不同的主体。
- roleRef,即用户主体待授予的角色。
将my-servicesaccount账号与pod-reader角色绑定,执行文件,创建rolebinding,查看状态
css
[root@k8s-master yaml]# kubectl get rolebinding
NAME ROLE AGE
pod-reader-rolebinding Role/pod-reader 10h
此时,我们进入Pod,再看下能否获取pod列表。
css
[root@k8s-master yaml]# kubectl exec -it pod-read sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
~ # APISERVER=https://kubernetes.default.svc
~ # SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
~ # NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
~ # TOKEN=$(cat ${SERVICEACCOUNT}/token)
~ # CACERT=${SERVICEACCOUNT}/ca.crt
~ # curl --cacert ${CACERT} -H "Authorization: Bearer $TOKEN" -X GET ${APISERVER}/api/v1/namespaces/default/pods
{
[
"metadata": {
"name": "taint-pod",
"namespace": "default",
"uid": "12603b58-86b9-4e95-a6d5-b5d8c86a5e9c",
"resourceVersion": "4812227",
...
]
...
}
可以看到,能正确的获取了。我们尝试删除其中一个pod
css
# curl --cacert ${CACERT} -H "Authorization: Bearer $TOKEN" -X DELETE ${APISERVER}/api/v1/namespaces/default/pods/busybox-pod
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods \"busybox-pod\" is forbidden: User \"system:serviceaccount:default:my-servicesaccount\" cannot delete resource \"pods\" in API group \"\" in the namespace \"default\"",
"reason": "Forbidden",
"details": {
"name": "busybox-pod",
"kind": "pods"
},
"code": 403
}
可以看到,由于没有授予删除相关的权限,删除指令被拒绝。
(3)ClusterRole和ClusterRoleBinding
由于NameSpace的隔离,对于某个空间的ServiceAccount是无法访问其他空间的资源对象的。如下图所示,default空间的my-serviceaccount账号是无法访问dev空间的Pod资源列表。
在实际工程中,又需要访问集群中的其他空间的资源,为了解决这一问题,K8S提供了ClusterRole和ClusterBinding。如下图所示:
ClusterRole和ClusterRoleBinding组合与Role以及RoleBinding组合的功能类似,只是它们属于整个集群,不属于具体某个命名空间,所以它们可以访问集群中任何命名空间的资源。接下来,我们来实现下这个例子。
首先创建一个NameSpace和属于该NameSpace的Pod,我们使用K8S初级入门系列之四-Namespace/ConfigMap/Secret中创建好的命名空间dev,以及ns-pod的Pod,可以查看下pod状态。
css
[root@k8s-master yaml]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
ns-pod 1/1 Running 0 8s
在没有使用ClusterRole和ClusterRoleBinding之前,我们看下能否从default空间中访问dev空间的资源。
css
[root@k8s-master yaml]# kubectl exec -it pod-read sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
~ # APISERVER=https://kubernetes.default.svc
~ # SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
~ # NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
~ # TOKEN=$(cat ${SERVICEACCOUNT}/token)
~ # CACERT=${SERVICEACCOUNT}/ca.crt
~ # curl --cacert ${CACERT} -H "Authorization: Bearer $TOKEN" -X GET ${APISERVER}/api/v1/namespaces/dev/pods
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods is forbidden: User \"system:serviceaccount:default:my-servicesaccount\" cannot list resource \"pods\" in API group \"\" in the namespace \"dev\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
访问被拒绝了,无法访问到其他命名空间pod。接下来,创建ClusterRole,命名为pod-reader-clusterrole
css
[root@k8s-master yaml]# cat pod-read-clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader-clusterrole
rules:
- apiGroups: [""] # "" 标明 core API 组
resources: ["pods"]
verbs: ["get", "watch", "list"]
与Role比较,ClusterRole没有NameSpace属性。执行该文件,创建完成后我们可以看下。
css
[root@k8s-master yaml]# kubectl get clusterrole
NAME CREATED AT
admin 2022-11-04T15:44:14Z
calico-kube-controllers 2022-11-05T03:09:48Z
calico-node 2022-11-05T03:09:48Z
cluster-admin 2022-11-04T15:44:14Z
edit 2022-11-04T15:44:14Z
ingress-nginx 2023-04-02T10:47:54Z
ingress-nginx-admission 2023-04-02T10:47:54Z
kubeadm:get-nodes 2022-11-04T15:44:15Z
kubernetes-dashboard 2022-11-05T09:53:47Z
pod-reader-clusterrole 2023-05-30T15:36:51Z
system:aggregate-to-admin 2022-11-04T15:44:14Z
system:aggregate-to-edit 2022-11-04T15:44:14Z
...
除了创建的pod-reader-clusterrole外,还可以看到大量的系统预置的ClusterRole。继续创建ClusterRoleBinding,命名为pod-reader-clusterrolebinding。
css
[root@k8s-master yaml]# cat pod-reader-clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: pod-reader-clusterrolebinding
subjects:
# 这里可以指定多个主体,User,ServiceAccount,Group
- kind: ServiceAccount
name: my-servicesaccount
namespace: default
apiGroup: ""
roleRef:
# "roleRef" 指定与某 Role 绑定关系
kind: ClusterRole
name: pod-reader-clusterrole
apiGroup: rbac.authorization.k8s.io
将my-serviceaccount账号与新创建的pod-reader-clusterrole角色绑定。
css
[root@k8s-master yaml]# kubectl get clusterrolebinding
NAME ROLE AGE
...
pod-reader-clusterrolebinding ClusterRole/pod-reader-clusterrole 16s
system:controller:attachdetach-controller ClusterRole/system:controller:attachdetach-controller 209d
system:controller:certificate-controller ClusterRole/system:controller:certificate-controller 209d
....
同样,除了我们创建的pod-reader-clusterrolebinding外,也存在大量的系统预置ClusterRoleBinding对象。再次进入default空间pod,访问dev空间的pod列表
css
[root@k8s-master yaml]# kubectl exec -it pod-read sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
~ # APISERVER=https://kubernetes.default.svc
~ # SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
~ # NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
~ # TOKEN=$(cat ${SERVICEACCOUNT}/token)
~ # CACERT=${SERVICEACCOUNT}/ca.crt
~ # curl --cacert ${CACERT} -H "Authorization: Bearer $TOKEN" -X GET ${APISERVER}/api/v1/namespaces/dev/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "31415719"
},
"items": [
{
"metadata": {
"name": "ns-pod",
"namespace": "dev",
"uid": "31bc247f-6c5a-496a-8805-2631f03e7df2",
"resourceVersion": "31324908",
"creationTimestamp": "2023-06-02T01:26:27Z",
"labels": {
"app": "nginx-pod"
},
...
此时可以正确访问pod列表了。
3、准入
突破了认证和鉴权两层关卡后,对于API的请求还需要通过"准入"这道关卡,K8S配备了一个准入控制器的插件列表,发送给API Server的任何请求都需要通过列表中每个准入控制器的检查,检查通不过,则拒绝调用请求。
准入控制器 是一段代码,它会在请求通过认证和鉴权之后、对象被持久化之前拦截到达 API 服务器的请求,准入控制器又可以分为验证(Validating)和变更(Mutating),变更(mutating)控制器可以根据被其接受的请求更改相关对象,Validating 控制器不会。如果任何一个阶段中的任何控制器拒绝了请求,则会立即拒绝整个请求,并将错误返回给最终的用户。
K8S定义了多个准入控制器,可以查看官方文档准入控制器参考 | Kubernetes,用户也可以自定义扩展插件。通过如下的命令可以查看K8S默认开启的准入控制器。
css
[root@k8s-master ~]# kubectl exec -it kube-apiserver-k8s-master -n kube-system -- kube-apiserver -h | grep enable-admission-plugins
.....
--enable-admission-plugins strings admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, PodSecurity, .....
稍后我们专门分析其中的PodSecurity准入控制器。
四、Pod安全策略
Pod除了对于API Server访问需要控制,Pod自身的安全策略也非常重要,因为Pod加载的容器镜像是由开发者,甚至是三方提供,如果不在Pod层进行安全的控制,这些镜像运行的代码就有可能利用漏洞实现非法的操作。
有些同学可能会问,Pod中的容器本来就是和宿主隔离的,就算有影响,也只会影响该容器的环境,其实不然,在前面的章节,我们了解到容器的目录是可以挂在到宿主节点上的,网络也可以直接使用宿主的,如果此时容器拥有root权限,就会随意篡改宿主节点的目录,或者利用宿主机网络进行恶意访问其他主机,从而影响集群安全。
为了解决这些安全问题,K8S对于Pod提供一系列的安全策略方案。
1、安全上下文配置
安全上下文即配置securityContext属性,Pod和Container上都可以配置该属性,两者的策略项既有重复的,也有不同的,对于重复的部分,Container策略会覆盖Pod上的。我们先来看下Pod上的配置。
(1)Pod配置
我们先看下没有配置securityContext属性时,Pod都有哪些权限。创建security-context-demo.yaml文件
css
[root@k8s-master yaml]# cat security-context-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: sec-ctx-vol
mountPath: /data/demo
执行文件,Pod创建完成后,进入容器内部,看下其默认的进程用户,组,以及文件目录属组。
css
[root@k8s-master yaml]# kubectl exec -it security-context-demo sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # id
uid=0(root) gid=0(root) groups=0(root),10(wheel)
/ # cd /data
/data # ls -l
total 4
drwxrwxrwx 2 root root 4096 Jun 4 05:58 demo
可以看到uid,gid,以及文件目录的属主都是root,为了安全起见,我们认为该Pod下的容器不需要root权限,那么就可以通过Pod级别的securityContext属性配置。下面我们修改yaml文件。
css
[root@k8s-master yaml]# cat security-context-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
volumes:
...
securityContext属性中有三项内容:
- runAsUser,指定容器中运行进程的用户(用户 ID )
- runAsGroup,指定容器中运行组(组 ID )
- fsGroup,文件属主组(组ID)
再进入容器看下
css
[root@k8s-master yaml]# kubectl exec -it security-context-demo sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ $ id
uid=1000 gid=3000 groups=2000,3000
/ $ cd /data
/data $ ls -l
total 4
drwxrwsrwx 2 root 2000 4096 Jun 4 06:30 demo
可以看到已经按照Pod的设置进行了更变。除了以上的三个配置项,Pod还可以设置其他的,具体参考:Kubernetes API Reference Docs
(2)容器配置
容器中也可以配置securityContext属性,首先我们看下runAsUser,runAsGroup,fsGroup属性。我们再来创建一个pod yaml
css
[root@k8s-master yaml]# cat security-context-demo1.yaml
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: sec-ctx-vol
mountPath: /data/demo
securityContext:
runAsUser: 2000
在container中,增加了securityContext属性,并配置了runAsUser了,其值和Pod的不同。我们进入Pod内部看下。
css
[root@k8s-master yaml]# kubectl exec -it security-context-demo1 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ $ id
uid=2000 gid=0(root) groups=0(root)
可以看到,Container定义的uid=2000覆盖了Pod定义的uid=1000。
默认的情况下,Pod是无法使用宿主内核的功能(容器镜像没有内核),比如修改网络接口,修改系统时间等等,如果获取这些权限,就需要申请授权特权模式。
我们先来看下在没有授权特权模式下,Pod操作内核指令的情况。 创建一个新的Pod,其yaml内容如下:
css
[root@k8s-master yaml]# cat security-context-demo2.yaml
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo2
spec:
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: sec-ctx-vol
mountPath: /data/demo
进入Pod,访问/dev目录下的设备列表,以及修改网络接口
css
[root@k8s-master yaml]# kubectl exec -it security-context-demo2 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # cd /dev
/dev # ls
core full null pts shm stdin termination-log urandom
fd mqueue ptmx random stderr stdout tty zero
/dev # ip link add dummy0 type dummy
ip: RTNETLINK answers: Operation not permitted
可以看到修改网络接口指令被拒绝了。dev目录下展示内容待会进行比较。接下来,我们修改yaml内容,增加特权配置( privileged: true)。
css
[root@k8s-master yaml]# cat security-context-demo2.yaml
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo2
spec:
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: sec-ctx-vol
mountPath: /data/demo
securityContext:
privileged: true
再次进入Pod,执行相关指令
css
[root@k8s-master yaml]# kubectl exec -it security-context-demo2 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # cd /dev
/dev # ls
autofs input raw tty12 tty26 tty4 tty53 ttyS0 vcs3 vhci
bsg kmsg rtc0 tty13 tty27 tty40 tty54 ttyS1 vcs4 vhost-net
bus loop-control sg0 tty14 tty28 tty41 tty55 ttyS2 vcs5 vhost-vsock
core mapper shm tty15 tty29 tty42 tty56 ttyS3 vcs6 watchdog
cpu mcelog snapshot tty16 tty3 ....
/dev # ip link add dummy0 type dummy
/dev #
可以看到,/dev下面显示多个特权设备 ,也正确的执行了网络接口修改指令。
特权虽然能支持内核操作,但是其权限粒度较大,从Linux内核2.2开始,引入了 Capabilities 机制来对 内核操作进行了更加细粒度的控制,可以实现按需授权,我们以修改网络接口为例。修改yaml文件如下:
css
[root@k8s-master yaml]# cat security-context-demo2.yaml
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo2
spec:
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: sec-ctx-vol
mountPath: /data/demo
securityContext:
capabilities:
add: # 添加
- NET_ADMIN
drop: # 删除
- KILL
删除了privileged: true配置,增加了capabilities的列表配置,这里仅需要网络权限,配置NET_ADMIN即可。
css
[root@k8s-master yaml]# kubectl exec -it security-context-demo2 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # ip link add dummy0 type dummy
可以正确的修改了网络接口配置。
容器的securityContext还支持seLinuxOptions,allowPrivilegeEscalation等其他配置项。完整的配置项参见:SecurityContext - Kubernetes指南
2、Pod准入策略
Pod一般都是有应用开发者配置和创建的,上面的securityContext配置是否都被允许的呢?比如说容器设置了特权模式,拥有了完整的操作内核的能力,集群管理员如何进行统一管控?这里就要用到上面介绍的Pod准入策略,其作用是,在Pod创建,进行准入校验。
在V1.21版本前,通过PodSecurityPolicy统一配置Pod的安全策略,该方式在V1.25中废弃,代替的是PodSecurity准入控制器。 PodSecurity定义了三种不同的策略,其限制程度逐级提升。
- Privileged,不受限制的策略,提供最大可能范围的权限许可。通常针对由特权较高、受信任的用户所管理的系统级或基础设施级负载。
- Baseline,限制性最弱的策略,禁止已知的策略提升。允许使用默认的(规定最少)Pod 配置。这种策略是最常见的,针对的是应用运维人员和非关键性应用的开发人员。其策略内容主要有,禁止有特权容器,禁止打破网络隔离等。
- Restricted,限制性非常强的策略,遵循当前的保护 Pod 的最佳实践。这类策略会牺牲一些兼容性,主要针对运维人员和安全性很重要的应用的开发人员,以及不太被信任的用户。其策略内容主要有,要求容器以非 root 用户运行, 容器组必须弃用 ALL capabilities ,并且只允许添加 NET_BIND_SERVICE 能力等。
目前PodSecurity仅限于这三种策略,且无法进行扩展的。这些策略需要应用于命名空间,实现对命名空间下所有Pod的约束,在命名空间中也可以配置三种模式。
- enforce,策略违例会导致 Pod 被拒绝
- audit,策略违例会触发审计日志中记录新事件时添加审计注解;但是 Pod 仍是被接受的。
- warn**,**策略违例会触发用户可见的警告信息,但是 Pod 仍是被接受的。
这三种模式配合上述的策略使用,命名空间可以配置多种策略。下面我们来看下案例。创建一个命名空间,设定安全策略。
css
[root@k8s-master yaml]# cat pod-level-ns.yaml
apiVersion: v1
kind: Namespace
metadata:
name: podlevel
labels:
#强制执行baseline安全标准,执行拒绝。适用于最新的k8s版本
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
#对restricted的Pod安全标准执行警告(warn)和审核(audit),适用于最新的k8s版本
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
在命名空间中增加了labels属性配置,下面是对其设置的解释。
# 设定模式及安全标准策略等级 # MODE必须是 `enforce`, `audit`或`warn`其中之一。 # LEVEL必须是`privileged`, `baseline`或 `restricted`其中之一 pod-security.kubernetes.io/<MODE>: <LEVEL> # 此选项是非必填的,用来锁定使用哪个版本的的安全标准 # MODE必须是 `enforce`, `audit`或`warn`其中之一。 # VERSION必须是一个有效的kubernetes minor version(例如v1.23),或者 `latest` pod-security.kubernetes.io/<MODE>-version: <VERSION>
本例中我们设置了三种模式,对于enforce,配置了baseline策略,也就是违反baseline策略的Pod一律拒绝执行;对于warn和audit,配置了restricted策略,也就是违反restricted策略的Pod进行警告和审核。
接下来,我们创建一个违反restricted策略的Pod。由于它的条件比较苛刻,默认的配置也无法校验通过。
css
[root@k8s-master yaml]# cat level-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: level-pod
namespace: podlevel
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
执行该文件,创建Pod
css
[root@k8s-master yaml]# kubectl apply -f level-pod.yaml
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
pod/level-pod created
[root@k8s-master yaml]# kubectl get pod -n podlevel
NAME READY STATUS RESTARTS AGE
level-pod 1/1 Running 0 78s
可以看到,违反了restricted策略,对用户进行了告警提示,但是Pod最终还是创建成功的。我们继续修改上面的Pod,让其违反baseline策略,baseline策略不允许有特权容器存在。
css
[root@k8s-master yaml]# cat level-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: level-pod
namespace: podlevel
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
securityContext:
allowPrivilegeEscalation: true
privileged: true
capabilities:
drop:
- ALL
执行该文件,可以看到,拒绝执行Pod创建。
css
[root@k8s-master yaml]# kubectl apply -f level-pod.yaml
Error from server (Forbidden): error when creating "level-pod.yaml": pods "level-pod" is forbidden: violates PodSecurity "baseline:latest": privileged (container "nginx" must not set securityContext.privileged=true)
综上所述,Pod安全上下文配置实现了对于容器镜像的约束,Pod准入策略,实现了Pod的约束。逐级构建了安全防护网,实现Pod安全。
五、总结
本篇我们介绍了K8S的安全相关内容,主要从API安全访问策略以及Pod安全策略两个方面。
API安全访问策略,主要是对API Server的访问安全控制,其设置了认证,鉴权,准入三道关卡。认证是针对用户的身份合法性验证,鉴权是对访问的API接口合法性验证,准入是通过一系列的准入控制器进行准入检查。只有经过了这三层关卡,才能访问API Server的资源。
Pod安全策略,主要介绍了安全上下文配置和Pod的准入检查。安全上下文配置是在Pod或者Container中设置securityContext属性;Pod的准入检查通过在命名空间下配置不同的策略,实现Pod创建时准入检查。
附:
K8S初级入门系列之四-Namespace/ConfigMap/Secret
K8S初级入门系列之六-控制器(RC/RS/Deployment)