一、前言

    安全是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,则使用该空间中默认的。

[root@k8s-master ~]# kubectl get sa
NAME                     SECRETS   AGE
default                  1         203d

(1)自定义ServiceAccount

当然,我们也可以自定义一个ServiceAccount,其yaml如下:

[root@k8s-master yaml]# cat my-serviceaccount.yaml 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-servicesaccount
  namespace: default

创建完成后,查看详情

[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的详情

[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内容如下:

[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目录。

[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内部,输入上述指令

[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列表。

 #   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文件,内容如下:

[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对象,查看状态。

[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内容如下:

[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,查看状态

[root@k8s-master yaml]# kubectl get rolebinding
NAME                                    ROLE                                         AGE
pod-reader-rolebinding                  Role/pod-reader                              10h

此时,我们进入Pod,再看下能否获取pod列表。

[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

# 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状态。

[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空间的资源。

[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

[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属性。执行该文件,创建完成后我们可以看下。

[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。

[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角色绑定。

[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列表

[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默认开启的准入控制器。

[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文件

[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创建完成后,进入容器内部,看下其默认的进程用户,组,以及文件目录属组。

[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文件。

[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)

再进入容器看下

[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

[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内部看下。

[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内容如下:

[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目录下的设备列表,以及修改网络接口

[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)。

[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,执行相关指令

[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文件如下:

[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即可。

[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 仍是被接受的。

      这三种模式配合上述的策略使用,命名空间可以配置多种策略。下面我们来看下案例。创建一个命名空间,设定安全策略。

[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。由于它的条件比较苛刻,默认的配置也无法校验通过。

[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

[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策略不允许有特权容器存在。

[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创建。

[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初级入门系列之一-概述

K8S初级入门系列之二-集群搭建

K8S初级入门系列之三-Pod的基本概念和操作

K8S初级入门系列之四-Namespace/ConfigMap/Secret

K8S初级入门系列之五-Pod的高级特性

K8S初级入门系列之六-控制器(RC/RS/Deployment)

K8S初级入门系列之七-控制器(Job/CronJob/Daemonset)

K8S初级入门系列之八-网络

K8S初级入门系列之九-共享存储

K8S初级入门系列之十-控制器(StatefulSet)

K8S初级入门系列之十一-安全

K8S初级入门系列之十二-计算资源管理

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐