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

一、前言

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

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

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

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

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

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

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

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

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

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

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

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

相关推荐
橙子家11 小时前
关于 K8s 的一些基础概念整理-补充【k8s系列之五】
k8s
于顾而言2 天前
【Enjoy Kubernetes】1. 基础入门
开发语言·云原生·k8s
roshy2 天前
POD 存储、PV、PVC
docker·k8s·pod
qq_338032923 天前
k8s总结
云原生·容器·kubernetes·k8s
KubeSphere 云原生3 天前
云原生周刊:Kubernetes v1.32 正式发布
云计算·k8s·容器平台·kubesphere
TiDB_PingCAP3 天前
知乎 PB 级别 TiDB 数据库集群管控实践
k8s·tidb·tidb operator
KubeSphere 云原生4 天前
拒绝 Helm? 如何在 K8s 上部署 KRaft 模式 Kafka 集群?
云计算·k8s·容器平台·kubesphere
年薪丰厚4 天前
如何查看K8S集群中service和pod定义的网段范围
docker·云原生·容器·kubernetes·k8s
麻辣翅尖8 天前
【解决】k8s使用flannel网络插件的问题整理
kubernetes·k8s·报错
kainx9 天前
AWS EKS 相关错误修复 - remote error: tls: internal error - CSR pending
kubernetes·云计算·k8s·aws·eks·csr