安全保障是基于软件全生命周期的,即贯穿了开发-分发-不熟-运行所有环节。在各个环节都可以进行一些安全活动保证系统安全。例如在代码开发环节从需求阶段即可加入安全的需求分析以及对应的保证策略,在代码提交前进行静态代码安全扫描、依赖扫描、IAC扫描等保证提交的代码安全。

在分发环节,image需要push到代码仓库上进行存储,那么对于代码仓库可以进行安全隔离,提交的image进行安全扫描,对镜像进行签名以及签名验证等保障push到registry的image是安全的。

在运行环节,也可遵循一些安全保障优秀实践,例如
分层对运行的应用实施安全保证,具体的措施如Network Policy,Pod Security,Container Security,Pod security Policy等等。

上面介绍了一些通用概念,接着将通过实际例子演示上面提到的一些具体安全保障措施如何保障运行时安全的。
第一:以Non-root身份运行容器
在DockerFile中通过User命令切换成非root用户,通过groupadd,useradd添加用户,通过USER username来制定后面的命令用非root用户来运行。
- FROM python:3.9.5-slim
- RUN pip install flask && \
- groupadd -r flask && useradd -r -g flask flask && \
- mkdir /src && \
- chown -R flask:flask /src
- USER flask
- COPY app.py /src/app.py
- WORKDIR /src
- ENV FLASK_APP=app.py
- EXPOSE 5000
- CMD ["flask", "run", "-h", "0.0.0.0"]
例如下面的实际例子,在Dockerfile中定义非root用户执行ENTRYPOINT命令
- FROM ubuntu
- ENV MY_SERVICE_PORT=80
- ENV MY_SERVICE_PORT1=80
- ENV MY_SERVICE_PORT2=80
- ENV MY_SERVICE_PORT3=80
- LABEL multi.label1="value1" multi.label2="value2" other="value3"
- ADD bin/amd64/httpserver /httpserver
- EXPOSE 8080
- RUN useradd -m --uid 1000 web-admin && \
- echo "web-admin ALL=NOPASSWD: ALL" >> /etc/sudoers
- RUN chown web-admin /httpserver
- USER web-admin
- ENTRYPOINT ["/httpserver"]
在部署文件中指定非root用户来运行容器。
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: non-root-httpserver
- spec:
- replicas: 1
- selector:
- matchLabels:
- app: httpserver
- template:
- metadata:
- annotations:
- prometheus.io/scrape: "true"
- prometheus.io/port: "80"
- labels:
- app: httpserver
- spec:
- securityContext:
- runAsUser: 1000
- containers:
- - name: httpserver
- imagePullPolicy: Always
- image: cncamp/httpserver:v1.0-nonroot
- ports:
- - containerPort: 80
- serviceAccount: fake-user
第二:集群间组件采用TLS加密进行安全通信,例如如果部署多个etcd的member,那么member之间访问可配置TLS加密,保证访问安全。

第三:添加NodeRestriction标签:准入控制器限制了kubelet可以修改Node和Pod对象,kubelet只可以修改自己的Node API对象,只能修改绑定到节点的Pod对象。可以对节点添加node-restriction.kubernetes.io前缀的标签,这样kubelet就不能删除节点了。
第四:Securtiy Policy:安全上下文描述了允许请求访问某个节点上的特定用户,获得特定权限访问主机网络等。Kubelets提供了三种配置的Security Context。
Container-level security context:仅应用到指定容器
Pod-leve security context:应用到Pod内所有的容器以及Volume
Pod Security Policies(PSP):应用到集群内部所有的Pod以及Volume
例如,查看被Istio注入的简单的toolbox的Pod的yaml文件,可以看到有securityContext的信息,drop all表示取消所有权限,1337进程号的User 来运行的容器。

再比如查看CoreDNS的pod的security context,有NET_BIND_SERVICE权限。

在看Calico pod的security context,因为要修改主机网络配置,权限比较大,privileged=true。

Pod Security Policies(PSP)是集群级的Pod安全策略,自动为集群内的Pod和Volume设置Security Context,Security Context设置的各字段和含义如下表所示

接下来通过实际例子演示如何通过PSP控制权限,PSP默认是不开启的,所以为了开启PSP,需要修改kube-apiserver.yaml中的配置信息,且因为开启后PSP就生效,所以提前通过Role/RoleBinding分配一个很大的权限,否则生效后很多操作都无法进行。启动前的PSP文件内容如下所示:这里的privileged=true,允许所有端口,所有Capalilities,任意用户运行。
- apiVersion: policy/v1beta1
- kind: PodSecurityPolicy
- metadata:
- name: privileged
- annotations:
- seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
- spec:
- privileged: true
- allowPrivilegeEscalation: true
- allowedCapabilities:
- - '*'
- volumes:
- - '*'
- hostNetwork: true
- hostPorts:
- - min: 0
- max: 65535
- hostIPC: true
- hostPID: true
- runAsUser:
- rule: 'RunAsAny'
- seLinux:
- rule: 'RunAsAny'
- supplementalGroups:
- rule: 'RunAsAny'
- fsGroup:
- rule: 'RunAsAny'
接着创建ClusterRole,这个Role的含义是:将名称为privileged的podsecuritypolicies use到这个role上,也就是role具备上面定义的PSP权限。然后通过RoleBinding将Role的权限赋值给kube-system namespace下面的所有serviceaccount和nodes对象。
- apiVersion: rbac.authorization.k8s.io/v1
- kind: ClusterRole
- metadata:
- name: privileged-psp
- rules:
- - apiGroups:
- - policy
- resourceNames:
- - privileged
- resources:
- - podsecuritypolicies
- verbs:
- - use
- ---
- apiVersion: rbac.authorization.k8s.io/v1
- kind: RoleBinding
- metadata:
- name: kube-system-psp
- namespace: kube-system
- roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: privileged-psp
- subjects:
- - apiGroup: rbac.authorization.k8s.io
- kind: Group
- name: system:nodes
- namespace: kube-system
- - apiGroup: rbac.authorization.k8s.io
- kind: Group
- name: system:serviceaccounts:kube-system
接着执行命令kubectl apply让上面的对象生效,修改/etc/kubernetes/manifests/kube-apiserver.yaml,开启PSP(--enable-admission-plugins=PodSecurityPolicy),配置文件修改后,APIServer会自动重启让配置生效。

因为配置了很大的权限,此时用kubectl执行各项操作仍然正常,接着来创建一个serviceaccount,对比serviceaccount绑定PSP前后的结果来理解PSP工作工程。
- kubectl create namespace psp-example
- kubectl create serviceaccount -n psp-example fake-user
- kubectl create rolebinding -n psp-example fake-editor --clusterrole=edit --serviceaccount=psp-example:fake-user
- ### create alias to simulate users
- ```
- alias kubectl-admin='kubectl -n psp-example'
- alias kubectl-user='kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example'
- ```
定义个名叫example的PSP,通过kubectl apply命令让这个PSP生效。
- apiVersion: policy/v1beta1
- kind: PodSecurityPolicy
- metadata:
- name: example
- spec:
- privileged: false # Don't allow privileged pods!
- # The rest fills in some required fields.
- seLinux:
- rule: RunAsAny
- supplementalGroups:
- rule: RunAsAny
- runAsUser:
- rule: RunAsAny
- fsGroup:
- rule: RunAsAny
- volumes:
- - '*'
接着用创建的serviceaccount去尝试创建一个pod
- kubectl-user create -f- <
- apiVersion: v1
- kind: Pod
- metadata:
- name: pause
- spec:
- containers:
- - name: pause
- image: k8s.gcr.io/pause
- EOF
会显示创建失败,查看该serviceaccount是否绑定了上面的PSP,结果是No。

接着创建Role和Rolebinding将PSP与serviceaccount进行绑定。
- ### create a role which use the psp
- kubectl-admin create role psp:unprivileged \
- --verb=use \
- --resource=podsecuritypolicy \
- --resource-name=example
-
-
- ### bind the role to user
- ```
- kubectl-admin create rolebinding fake-user:psp:unprivileged \
- --role=psp:unprivileged \
- --serviceaccount=psp-example:fake-user
- ```
接着再用上面的serviceaccount创建pod,可以看到创建pod成功。
通过上面的例子可以看到通过定义不同权限的PSP,再将PSP的权限分配给不同的serviceaccount,从而来保障集群内的安全。
除了上面介绍了PSP,实际给节点增加Taint也是一种安全防护措施。例如:为不同的租户的节点增加Taint,这样多租户情况下实现节点的相互隔离。