本文主要介绍如何正确配置Pod安全来加固集群的安全性,防止集群成为攻击者利用的目标。
防止进程逃离容器边界并获得权限
作为使用Kubernetes的开发或者运维人员,您的主要关注点应该是如何防止在容器中运行的进程逃离容器的隔离边界并获得对宿主机的访问权限。这样做主要有两个原因。首先,容器内运行的进程默认在[Linux] root
用户的上下文中运行。尽管 root
在容器中的操作部分受到Docker分配给容器的Linux capabilities
的限制,但这些默认权限可能允许攻击者提权或者访问到宿主机的敏感信息,包括Secrets
和ConfigMap
等敏感资源。下面是分配给Docker容器的默认capabilities
列表。更多信息,请参见capabilities(7) — Linux manual page。
cap_chown, cap_dac_override, cap_fowner, cap_fsetid, cap_kill, cap_setgid, cap_setuid,
cap_setpcap, cap_net_bind_service, cap_net_raw, cap_sys_chroot, cap_mknod, cap_audit_write,
cap_setfcap
.
应尽可能避免使用以特权身份(privileged
)运行Pod,因为其拥有与宿主机上root关联的所有Linux capabilities
。
其次,所有Kubernetes工作节点都使用一种称为节点授权者的授权模式。节点授权者授权所有源自Kubelet的API请求,并允许节点执行以下操作:
- Services
- Endpoints
- Nodes
- Pods
- 与绑定到Kubelet节点的Pod相关的Secrets、Configmaps、PV和PVC
- 节点和节点状态(启用
NodeRestriction
准入插件以限制Kubelet修改自己的节点) - Pods和Pod状态(启用
NodeRestriction
准入插件以限制Kubelet修改绑定到自身的Pod) - Events
- 对用于TLS引导的
CertificateSigningRequest (CSR)
API的读/写访问权限 - 能够为委托的身份验证/授权检查创建
TokenReview
和SubjectAccessReview
ACK集群默认使用节点限制准入控制器。该控制器仅允许节点修改绑定到节点的一组有限节点属性和Pod对象,但是设法访问主机的攻击者仍然能够从Kubernetes API收集环境中的敏感信息。更多信息,请参见节点限制准入控制器。
PSP(PodSecurityPolicy)在Kubernetes1.21版本中被设置为Deprecated状态,在使用中的用户可以在1.25版本前有一个替换的缓冲期。社区正在计划通过新的内置准入控制器方案来取代PSP,ACK容器服务也将通过基于OPA的策略治理方案逐步替换正在使用的PSP。
Pod安全配置建议
- 限制容器以特权模式运行
如前所述,以特权身份运行的容器继承了分配给主机上root的所有Linux capabilities。大多数场景下,容器并不是必须这些权限才能保证业务运行。您可以通过创建Pod安全策略来拒绝容器配置为以特权模式运行的Pod。您可以将Pod安全策略视为Pod在创建之前必须满足的一组安全约束。Kubernetes的Pod安全策略(Pod Security Policy)准入控制组件会基于您定义的规则验证在集群上创建和更新Pod的请求。如果创建或更新Pod的请求不符合定义的规则,系统将拒绝该请求并返回错误。
ACK集群默认启用PSP准入控制插件,并配置一个名为
ack.privileged
的Pod安全策略。这个安全策略将放行任意类型的Pod。作为安全最佳实践,我们建议您根据权限最小化原则以集群命名空间为维度细粒度管理应用对应的PSP策略,例如约束指定命名空间下禁止部署特权容器,只能使用只读的根文件系统,或者只能挂载指定范围的host目录。一个PSP策略模板如下:apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: restricted annotations: seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default' apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default' apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' spec: privileged: false # 防止升级到根目录所需。 allowPrivilegeEscalation: false requiredDropCapabilities: - ALL # 允许核心卷类型。 volumes: - 'configMap' - 'emptyDir' - 'projected' - 'secret' - 'downwardAPI' # 假设集群管理员设置的PersistentVolume可以安全使用。 - 'persistentVolumeClaim' hostNetwork: false hostIPC: false hostPID: false runAsUser: # 要求容器在没有root权限的情况下运行。 rule: 'MustRunAsNonRoot' seLinux: # 此策略假定节点使用AppArmor而不是SELinux。 rule: 'RunAsAny' supplementalGroups: rule: 'MustRunAs' ranges: # 禁止添加根组。 - min: 1 max: 65535 fsGroup: rule: 'MustRunAs' ranges: # 禁止添加根组。 - min: 1 max: 65535 readOnlyRootFilesystem: false
上述策略实例可防止Pod以特权或特权提升的模式运行。还限制了可以挂载的卷类型和可以添加的根补充组。您可以根据需要进一步加强策略约束,更多信息,请参见Pod Security Policies。
- 限制应用程序进程以root身份运行
默认情况下容器都以
root
身份运行。如果攻击者能够利用应用程序中的漏洞并获得正在运行的容器的shell
访问权限,这可能会出现安全问题。您可以通过多种方式缓解此类风险。一种方式是,通过从容器镜像中删除shell
。另一种方式是,将USER
指令添加到您的Dockerfile
或以非root
用户身份在Pod中运行容器。KubernetespodSpec
在spec.securityContext
下包含和runAsUser
和runAsGroup
两个字段,允许您指定运行应用程序的用户和组。您可以通过创建Pod安全策略来强制使用这些字段。更多信息,请参见Users and groups。 - 禁止以Docker in Docker的方式运行容器或者在容器中挂载Docker.sock
使用嵌套容器的方式或者挂载Docker.sock可以方便地在Docker容器中构建/运行容器镜像,但您将节点的控制权交给了在容器中运行的进程。关于在Kubernetes上构建容器镜像,请参见使用容器镜像服务企业版构建镜像、Kaniko、img。
- 限制使用HostPath,如果需要使用HostPath,限制只可以挂载指定前缀的目录并将卷配置为只读使用
HostPath
可以直接将宿主机的目录挂载到容器中。很少有业务场景的Pod用到此功能特性。但如果确实有业务需要,您需要了解其中的风险。默认情况下,以root身份运行的Pod将拥有对HostPath
暴露的文件系统的写访问权限。这可能允许攻击者修改Kubelet
设置,创建指向未直接通过HostPath
暴露的目录或文件的符号链接,例如/etc/shadow
、安装Ssh密钥、读取挂载到主机的密钥以及进行恶意操作。为了降低使用HostPath
的风险,请将spec.containers.volumeMounts
配置为只读,例如:volumeMounts: - name: hostPath-volume readOnly: true mountPath: /host-path
您还可以使用Pod安全策略来限制hostPath
卷可以挂载的目录。例如,以下PSP策略仅允许挂载宿主机中以/foo
开头的路径。allowedHostPaths: # This allows "/foo", "/foo/", "/foo/bar" etc., but # disallows "/fool", "/etc/foo" etc. # "/foo/../" is never valid. - pathPrefix: "/foo" readOnly: true # only allow read-only mounts
- 为每个容器设置请求和资源限制,避免资源争夺或DoS攻击
没有请求或资源限制的Pod理论上可以消耗掉主机上的所有可用资源。当有Pod被调度到此节点上时,该节点可能会遭遇CPU或内存不足的情况,这可能导致
Kubelet
崩溃或从节点驱逐Pod。虽然无法完全避免这种情况的发生,但设置请求和资源限制将有助于最大程度地减少资源争夺,并降低应用程序编写不当导致资源消耗过多所带来的风险。PodSpec允许您限制CPU和内存的使用。您可以通过在命名空间上设置Resource Quota或创建Limit Range来强制对请求和资源进行限制。资源配额允许您指定分配给命名空间的资源总量,例如CPU和RAM。当应用于命名空间时,会强制您为部署到该命名空间中的所有容器指定请求和资源的限制。相比之下,限制范围可让您更精细地控制资源分配。通过限制范围,您可以为命名空间内的每个Pod或每个容器的CPU和内存资源设置最小值/最大值。如果没有提供,您还可以设置默认请求/限制值。更多信息,请参见Managing Resources for Containers。
- 禁止使用特权提升配置
特权提升允许进程更改其运行所在的安全上下文。例如
sudo
,带有SUID
或SGID
位的二进制文件也是如此。特权升级基本上是用户以另一个用户或组的权限执行文件的一种方式。 您可以通过将allowPriviledgedEscalation
设置为false
的pod
安全策略或通过在podSpec
中设置securityContext.allowPrivilegedEscalation
来阻止容器进行特权提升。 - 禁用Service Account令牌自动挂载对于不需要访问Kubernetes API的Pod,您可以在
PodSpec
上禁止自动挂载ServiceAccount
令牌,或禁用所有使用特定ServiceAccount
的Pod。apiVersion: v1 kind: Pod metadata: name: pod-no-automount spec: automountServiceAccountToken: false
禁用ServiceAccount
自动挂载不会阻止Pod对Kubernetes API的网络访问。为阻止Pod对Kubernetes API进行网络访问,您需要修改ACK集群Endpoint
访问并使用网络策略Network Policy来阻止Pod对Kubernetes API进行网络访问。具体操作,请参见使用网络策略Network Policy。apiVersion: v1 kind: ServiceAccount metadata: name: sa-no-automount automountServiceAccountToken: false
- 禁用服务发现
对于不需要查找或调用集群服务的Pod,您可以减少提供给Pod的信息量。您可以将Pod的DNS策略设置为不使用CoreDNS,并且不将命名空间中的Service暴露为Pod中的环境变量。更多信息,请参见Environment variables。
Pod的DNS策略的默认值是
ClusterFirst
,使用集群内DNS,而非默认值Default
使用底层节点的DNS解析。更多信息,请参见Kubernetes docs on Pod DNS policy。禁用服务链接和更改Pod的DNS策略不会阻止Pod对集群内DNS服务进行网络访问。攻击者仍然可以通过访问集群内DNS服务来枚举集群中的服务(例如:
dig SRV *.*.svc.cluster.local @$CLUSTER_DNS_IP
)。关于阻止集群内服务发现,请参见使用网络策略Network Policy。apiVersion: v1 kind: Pod metadata: name: pod-no-service-info spec: dnsPolicy: Default # “默认值”不是真正的默认值。 enableServiceLinks: false
- 配置镜像为只读文件系统将您的镜像配置为只读文件系统可防止攻击者覆盖您的应用程序使用的文件系统上的文件。如果您的应用程序必须写入文件系统,请考虑写入临时目录或挂载附加卷。您可以通过如下设置Pod的
SecurityContext
来强制执行此操作:... securityContext: readOnlyRootFilesystem: true ...