大家好!我是大聪明-PLUS

Kubernetes 中的安全上下文允许您在 Pod 或容器级别配置安全设置。其中一些设置相当简单易懂,而另一些则较为晦涩难懂。

我们深知使用 Kubernetes 时安全性的重要性。由于所有工作负载都在主机操作系统(节点)上运行,因此保护这些工作负载至关重要。如果攻击者成功逃离隔离容器并获得主机访问权限,不仅节点本身及其所有 Pod 都面临风险,整个集群,甚至可能是整个企业网络,都将面临风险。

在本文中,创建了一份在 Kubernetes 中创建多层主机安全系统的实用指南。我们将探讨两个关键的防御层:

  • 在 Kubernetes 规范层面:使用 Security Context 等内置机制来限制容器的权限和能力。

  • 在 Linux 内核级别,AppArmor 和 seccomp 等强大安全工具的集成可以对 pod 与主机操作系统的交互方式进行精细控制。

所提供的建议和示例将帮助您显著加强基础设施安全并最大限度地降低与容器化应用程序泄露相关的风险。

Kubernetes 级安全性

Kubernetes 安全至关重要,其首要任务是保护 Kubernetes 主机免受其上运行的容器的侵害。如果攻击者入侵了容器或 Pod,他们就有多个接入点来攻击主机本身。如果他们成功入侵了主机操作系统,他们就可以攻击集群中的其他节点(更不用说该节点上运行的所有 Pod 和应用程序)。在最坏的情况下,他们甚至可以访问您网络上的其他系统。以下部分将讨论如何保护主机操作系统。

操作系统命名空间

从安全角度来看,容器利用操作系统命名空间。此概念不应与 Kubernetes 命名空间混淆。在此上下文中,命名空间是 Linux 内核的一项功能,容器技术使用它来区分一个容器与在同一台计算机上运行的其他容器以及主机操作系统。

Kubernetes 服务器具有主机命名空间。这些命名空间供直接在主机上运行的常规应用程序使用。相比之下,容器在其独立的 Linux 内核命名空间中运行。这种组织方式不仅在容器之间提供隔离,还在容器与主机操作系统之间提供隔离。

主机操作系统命名空间

主机操作系统命名空间

如果容器被攻陷,攻击者的活动将被限制在该容器的范围内(其命名空间)。这种隔离可以防止攻击者访问主机或其他容器并对其进行攻击。

为什么这很重要?可以将 Pod 配置为使用主机的共享命名空间,而不是其自身的隔离命名空间。如果容器需要直接与主机操作系统通信,则可能需要这样做。但是,由于存在安全风险,这应该只在最极端的情况下进行。如果攻击者入侵了这样的容器,他们将有更多机会与操作系统组件交互,因为容器的隔离将不再成为障碍。

以下是允许 pod 直接与主机操作系统通信的配置:

apiVersion: v1
kind: Pod
metadata:
  name: host-pod
spec:
  hostIPC: true
  hostNetwork: true
  hostPID: true
  containers:
  - name: nginx
    image: nginx

如果设置spec.hostIPCtrue,容器将使用主机命名空间进行进程间通信(IPC)。

进程间通信是 Linux 中的一种机制,允许进程之间相互通信。容器通常使用自己独立的 IPC 命名空间,从而消除了容器与主机操作系统上进程之间的通信。另一个设置spec.hostNetwork控制网络命名空间。[IPC]spec.hostPID指示容器使用主机进程 ID (PID) 空间。所有这些设置都指示容器使用相应的主机命名空间。默认情况下,它们都设置为false。如果不进行修改,容器将使用隔离的命名空间。

不要以特权模式运行 Pod

保护主机操作系统安全的另一个重要考虑因素是特权执行。特权执行赋予容器最高的权限。这使得容器可以提升权限并访问主机级资源——几乎就像进程直接在主机上运行一样。

下图展示了在同一主机上运行的非特权容器和特权容器。如果非特权容器被攻陷,攻击者通常无法访问主机的资源。特权容器则有所不同——攻陷特权容器可以让攻击者直接访问系统。

特权容器和非特权容器

特权容器和非特权容器

spec.containers[*].SecurityContext.privileged要创建特权容器,请在truepod 定义中设置字段:

apiVersion: v1
kind: Pod
metadata:
  name: privileged-pod
spec:
  containers:
  - name: nginx
    image: nginx
    securityContext:
      privileged: true

了解 Linux 的功能

要考虑的第三个概念是Linux功能。这些能力会向进程授予特定权限,但不会授予其完全的超级用户 (root) 权限。默认情况下,容器运行时会使用容器运行时定义的一组基本能力运行,这通常足以满足大多数用例的需求。根据最小权限原则,建议从 Pod 配置中删除除其真正需要的能力之外的所有能力。授予 Pod 过多的权限可能会对主机操作系统造成安全风险。

要明确删除 pod 中的所有功能,请将参数设置spec.containers[*].securityContext.capabilities.dropALL

apiVersion: v1
kind: Pod
metadata:
  name: capabilities-pod
spec:
  containers:
  - image: busybox
    name: busybox
    command:
      - sleep
      - "3600"
    securityContext:
      capabilities:
        drop:
          - ALL

无需 root 权限即可运行容器

我们最后要讨论的是runAsUser:容器可以在任何 Linux 用户下运行。如果容器以 root 用户身份运行(即runAsUser: 0),并且遭到黑客攻击,攻击者很可能会获得主机操作系统的 root 访问权限。有了此访问权限,攻击者可以接管整个 Kubernetes 集群或其他公司系统。

以下 pod 配置降低了以 root 身份运行容器的风险:

apiVersion: v1
kind: Pod
metadata:
  name: capabilities-pod
spec:
  containers:
  - image: busybox
    name: busybox
    command:
      - sleep
      - "3600"
    securityContext:
      runAsNonRoot: true
      runAsUser: 1000
      runAsGroup: 1000

如果此字段spec.containers[*].securityContext.runAsNonRoot设置为true,容器将以非 root 用户身份运行。另外两个字段spec.containers[*].securityContext.runAsUserspec.containers[*].securityContext.runAsGroup定义了应用程序在容器内运行的用户和组。为了确保应用程序以非 root 用户身份运行,除了 Pod 定义中的设置外,还需要在为应用程序构建 Docker 镜像时在 Dockerfile 中指定用户和组。

使用 Linux 内核模块加强隔离

一些 Linux 内核强化工具可以与 Kubernetes 集成,以控制 Pod 和容器与主机操作系统的交互方式。例如,您可以阻止 Pod 创建文件或运行程序。下面,我们将介绍两个这样的工具:AppArmor 和 seccomp。我们将展示如何使用它们来限制 Kubernetes 与主机操作系统之间的某些操作。

AppArmor

AppArmor 是一个 Linux 内核安全模块,允许对 Linux 系统上运行的程序的访问权限进行精细配置。AppArmor 配置文件包含一些规则,用于指定程序可以执行的操作和不可以执行的操作。

AppArmor配置文件在服务器级别加载,可以通过两种模式之一激活。第一种模式是complain通知模式。在此模式下,AppArmor 不会阻止任何操作,而只会创建有关程序执行操作的报告。这可用于确定 Pod 正在运行哪些命令或功能。第二种模式是enforce强制模式。在此模式下,AppArmor 将主动阻止任何配置文件不允许的 Pod 操作。务必在每个工作节点上激活 AppArmor 配置文件。

将 AppArmor 配置文件应用于 Pod

让我们创建一个拒绝所有磁盘写入的 AppArmor 配置文件,并将此配置文件应用于 Pod。在工作节点上,创建一个名为 k8s-deny-write 的文件,其内容如下:  

#include <tunables/global>
profile k8s-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>
  file,
  deny /** w,
}

您可以使用命令 激活 AppArmor 配置文件apparmor_parser。默认情况下,配置文件以 模式加载enforce。要在 模式下激活,complain请使用标志-C

>sudo apparmor_parser ./k8s-deny-write

您可以使用以下命令检查配置文件是否已加载aa-status

>sudo aa-status
apparmor module is loaded.
56 profiles are loaded.
52 profiles are in enforce mode.
...
k8s-deny-write
...
0 processes are unconfined but have a profile defined.

使用以下清单,我们将创建一个打印简单消息的 pod:

apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor
spec:
  containers:
  - name: hello
    image: busybox:1.28
    command: [ "sh", "-c", "while true; do echo 'Hello AppArmor!' > /tmp/hello && cat /tmp/hello; sleep 10; done" ]

创建 Pod 后,我们来查看一下它的日志。记录了以下消息:

>kubectl logs hello-apparmor -f
Hello AppArmor!
Hello AppArmor!

说明:
在 Kubernetes v1.30 之前,AppArmor 是使用注解进行配置的。

现在让我们通过 Pod 清单文件配置 AppArmor 配置文件securityContext。新的清单文件如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor
spec:
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: k8s-deny-write 
  containers:
  - name: hello
    image: busybox:1.28
    command: [ "sh", "-c", "while true; do echo 'Hello AppArmor!' > /tmp/hello && cat /tmp/hello; sleep 10; done" ]

让我们删除当前的 pod,应用新的清单,然后再次检查日志:

>kubectl delete pods hello-apparmor 
pod "hello-apparmor" deleted

>kubectl apply -f apparmor.yaml 
pod/hello-apparmor created

>kubectl logs hello-apparmor -f
sh: can't create /tmp/hello: Permission denied
sh: can't create /tmp/hello: Permission denied
sh: can't create /tmp/hello: Permission denied

由于 AppArmor 配置文件的原因, hello-apparmor命令行无法创建文件。您还可以使用以下命令检查是否已应用该配置文件:

>kubectl exec hello-apparmor -- cat /proc/1/attr/current
k8s-deny-write (enforce)

在这里您可以看到k8s-deny-write配置文件已被应用并且正在运行enforce

为了总结本节,我们来看看如果为尚未加载的 Pod 指定配置文件会发生什么。让我们创建这样一个 Pod:

>kubectl create -f /dev/stdin <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor-2
spec:
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: k8s-apparmor-example-allow-write
  containers:
    - name: hello
      image: busybox:1.28
      command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created

让我们检查一下它的状态:

>kubectl get pods hello-apparmor-2
NAME               READY   STATUS                 RESTARTS   AGE
hello-apparmor-2   0/1     CreateContainerError   0          12s

让我们用它kubectl describe来找出错误:

>kubectl describe pods hello-apparmor-2
...
  Warning  Failed     79s (x12 over 3m15s)  kubelet            Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found k8s-apparmor-example-allow-write
...

事件部分清楚地显示未找到 AppArmor 配置文件。因此,除非该配置文件已在节点上预加载,否则 Pod 将无法启动。

Seccomp

Seccomp 是一项 Linux 内核功能,允许您限制应用程序可用的系统调用。它可以通过限制应用程序与操作系统的交互来提高应用程序的安全性,从而减少潜在的攻击面。这可以通过定义一个过滤器来实现,该过滤器指定允许哪些系统调用,禁止哪些系统调用。

在 Kubernetes 中,seccomp可用于定义 Pod 安全策略。这可确保 Pod 仅使用其运行所需的系统调用,从而降低漏洞被利用的风险。

seccomp 配置文件示例

让我们下载三个示例 seccomp 配置文件来测试我们的 Pod。这些文件应位于 Kubernetes 集群的每个节点上。第一个配置文件audit.json记录所有进程系统调用。第二个配置文件violation.json拒绝所有系统调用。第三个配置文件fine-grained.json允许 block 中的一组特定系统调用action: SCMP_ACT_ALLOW。要将它们下载并保存到本地目录/var/lib/kubelet/seccomp/seccomp_profiles,请运行以下命令:

>sudo mkdir -p /var/lib/kubelet/seccomp/seccomp_profiles

>curl -L -o seccomp_profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json 

>curl -L -o seccomp_profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json 

>curl -L -o seccomp_profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json

>ls seccomp_profiles/
audit.json  fine-grained.json  violation.json
创建一个记录所有系统调用的 pod

首先,让我们在.spec.securityContextPod 的字段中配置 audit.json 配置文件。下面是将使用它的 Pod 清单:

apiVersion: v1
kind: Pod
metadata:
  name: audit-pod
  labels:
    app: audit-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: seccomp_profiles/audit.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

让我们创建一个 pod:

>kubectl apply -f audit.yaml  
pod/audit-pod created

此配置文件不会阻止任何内容,因此 Pod 可以顺利启动。现在,让我们连接到工作节点,看看audit-podsyslog正在进行哪些系统调用:

>sudo tail -f /var/log/syslog | grep 'http-echo'

Jul 23 22:09:48 5600791c132c kernel: [ 2070.311535] audit: type=1326 audit(1721772588.695:700): auid=4294967295 uid=65532 gid=65532 ses=4294967295 subj=cri-containerd.apparmor.d pid=16021 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=35 compat=0 ip=0x4685d7 code=0x7ffc0000

Jul 23 22:09:48 5600791c132c kernel: [ 2070.311662] audit: type=1326 audit(1721772588.695:701): auid=4294967295 uid=65532 gid=65532 ses=4294967295 subj=cri-containerd.apparmor.d pid=16021 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x468ba3 code=0x7ffc0000
创建一个具有禁用所有系统调用的配置文件的 pod。

violation.json配置文件会禁用所有系统调用。如果将其应用于 Pod,它将无法启动。以下 Pod 使用了此配置文件:

apiVersion: v1
kind: Pod
metadata:
  name: violation-pod
  labels:
    app: violation-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: seccomp_profiles/violation.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

让我们创建它并检查其状态:

>kubectl apply -f violation.yaml 
pod/violation-pod created

>kubectl get pods violation-pod 
NAME            READY   STATUS              RESTARTS     AGE
violation-pod   0/1     RunContainerError   2 (1s ago)   26s

查看后syslog,我们看到一个错误,指出 pod 无法启动其进程:

>sudo tail -f /var/log/syslog | grep 'http-echo'

Jul 23 22:28:14 5600791c132c kubelet[769]: E0723 22:28:14.220131     769 kuberuntime_manager.go:1256] container &Container{Name:test-container,Image:hashicorp/http-echo:1.0,Command:[],Args:[-text=just made some syscalls!],WorkingDir:,Ports:[]ContainerPort{},Env:[]EnvVar{},Resources:ResourceRequirements{Limits:ResourceList{},Requests:ResourceList{},Claims:[]ResourceClaim{},},VolumeMounts:[]VolumeMount{VolumeMount{Name:kube-api-access-lmsz5,ReadOnly:true,MountPath:/var/run/secrets/kubernetes.io/serviceaccount,SubPath:,MountPropagation:nil,SubPathExpr:,RecursiveReadOnly:nil,},},LivenessProbe:nil,ReadinessProbe:nil,Lifecycle:nil,TerminationMessagePath:/dev/termination-log,ImagePullPolicy:IfNotPresent,SecurityContext:&SecurityContext{Capabilities:nil,Privileged:nil,SELinuxOptions:nil,RunAsUser:nil,RunAsNonRoot:nil,ReadOnlyRootFilesystem:nil,AllowPrivilegeEscalation:*false,RunAsGroup:nil,ProcMount:nil,WindowsOptions:nil,SeccompProfile:nil,AppArmorProfile:nil,},Stdin:false,StdinOnce:false,TTY:false,EnvFrom:[]EnvFromSource{},TerminationMessagePolicy:File,VolumeDevices:[]VolumeDevice{},StartupProbe:nil,ResizePolicy:[]ContainerResizePolicy{},RestartPolicy:nil,} start failed in pod violation-pod_default(9743bb58-804b-40c8-9775-babfa69e80d1): RunContainerError: failed to start containerd task "28a0542f2cb20f3747de7518a1bafeeb0c470bd0cbac07482f4bca82f1b32279": cannot start a stopped process: unknown

正如预期的那样,seccomp 配置文件阻止了 pod 执行任何系统调用。

我们创建一个具有配置文件的 pod,它允许我们执行必要的系统调用。

要成功运行镜像http-echo,需要获得执行某些系统调用的权限。fine -grained.json配置文件允许执行必要的系统调用。以下是使用此配置文件的示例 Pod 清单:

apiVersion: v1
kind: Pod
metadata:
  name: fine-grained-pod
  labels:
    app: fine-grained-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: seccomp_profiles/fine-grained.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

让我们创建一个 pod 并检查其状态:

>kubectl apply -f fine-grained.yaml 
pod/fine-grained-pod created

>kubectl get pods fine-grained-pod 
NAME               READY   STATUS    RESTARTS   AGE
fine-grained-pod   1/1     Running   0          67s

由于配置文件允许图像所需的所有系统调用,因此syslog没有错误并且 pod 成功启动。

结论

本文探讨了在 Kubernetes 环境中保护主机操作系统的全面方法,这对于确保整体集群安全至关重要。可靠的安全性建立在多层方法之上,至少包含两个级别。

首先是使用原生 Kubernetes 工具。通过安全上下文强制执行最小权限原则是基本的安全措施。您应该始终:

  • 避免特权模式和直接访问主机命名空间(hostIPChostNetworkhostPID);

  • 限制对 pod 未明确要求的 Linux 功能的访问;

  • 以非 root 用户身份运行容器。

第二层是使用专门的 Linux 内核模块来强化安全性。例如AppArmorseccomp等工具访问。即使攻击者设法在应用程序中发现漏洞,这也能显著减少攻击面。

这些方法的组合创建了一个强大的屏障,有效地将容器与主机以及容器彼此隔离。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐