LabHub
学习 学习路径 课程

CKS — Kubernetes 安全专家

用 seccomp、AppArmor、capability 收紧

在 LabHub 中继续学习

目标

掌握仅通过 Pod 规范减少容器可向内核请求的操作,并建立检测集群中遗留 privileged 容器的流程。

为什么重要

容器共享主机内核。逃逸漏洞大多通过系统调用或 capability 攻击内核。因此,防御不应只着眼于阻止入侵,而应预先减少入侵进程能够执行的操作。seccomp 限制可调用的系统调用,AppArmor 限制可访问的文件与功能,capabilities 限制 root 权限的各个部分。三层相互叠加,即使一层被突破,其他层仍在。反之,privileged: true 会一次性使三层失效,因此统计生产集群中的 privileged 容器就是在衡量风险。

注意:此实验环境没有真实容器运行时,无法确认 seccomp、AppArmor 配置文件是否真正强制执行,也不检查节点上是否存在配置文件。评分只检查 Pod 规范字段是否准确。CKS 考试最终检查的也是这些规范。

步骤

  1. 创建命名空间 cks-sys,并在其中创建 Pod seccomp-default(镜像 nginx:1.27-alpine)。Pod 层级 spec.securityContext.seccompProfile.typeRuntimeDefault

  2. cks-sys 中创建 Pod seccomp-custom。Pod 层级 seccompProfile 的 typeLocalhostlocalhostProfileprofiles/audit.json

  3. cks-sys 中创建 Pod apparmor-app(容器名称 app,镜像 nginx:1.27-alpine),并应用 AppArmor 配置文件 k8s-custom-profile。容器 securityContext.appArmorProfiletypeLocalhostlocalhostProfilek8s-custom-profile。(使用旧格式注解 container.apparmor.security.beta.kubernetes.io/app: localhost/k8s-custom-profile 也可通过。)

  4. cks-sys 中创建 Pod capped(容器名称 app)。容器 securityContext 中 capabilities.drop 只有 ALLcapabilities.add 只有 NET_BIND_SERVICEallowPrivilegeEscalationfalse

  5. cks-sys 中创建 Pod no-host-ns。显式将 hostNetworkhostPIDhostIPCshareProcessNamespace 全部设为 false

    这里隐藏着审计必须了解的事实。PodSpec 中前三个字段是 bool + omitempty,即使写为 false,序列化时也会完全消失。因此,仅查看保存的对象无法区分“显式写了 false”和“完全没写”。真正的审计标准不是“是否写了 false”,而是是否不为 true。而 shareProcessNamespace*bool,所以 false 会保留——评分也反映了这一区别。应用后用 kubectl get pod no-host-ns -n cks-sys -o yaml 亲自确认。

  6. cks-sys 中创建 Pod legacy-agent(容器名称 agent),并将容器 securityContext 的 privileged 设为 true,以复现旧工作负载。然后在 /root/cks-system-hardening/find-privileged.sh 中编写检测脚本,将结果保存到 /root/cks-system-hardening/privileged.txt。结果应列出 cks-sys 命名空间中包含 privileged 容器(包括 init 容器)的 Pod,格式为 cks-sys/<파드이름>,按字典序排序,每行一个。

  7. cks-sys 中创建 Pod hardened(容器名称 app)。容器 securityContext 同时设置 readOnlyRootFilesystem: truerunAsNonRoot: truerunAsUser: 1000allowPrivilegeEscalation: falsecapabilities.drop: [ALL];Pod 层级 seccompProfile.typeRuntimeDefault。将需要写入的 /tmp 挂载为名为 tmp 的 emptyDir 卷。

参考

运行时默认 seccomp 配置文件

创建命名空间 cks-sys,并在其中创建 Pod seccomp-default(镜像 nginx:1.27-alpine)。Pod 层级 spec.securityContext.seccompProfile.typeRuntimeDefault

seccompProfile 可用于 Pod 层级 spec.securityContext 或容器层级。本步骤使用 Pod 层级。

指定自定义 seccomp 配置文件

cks-sys 中创建 Pod seccomp-custom。Pod 层级 seccompProfile 的 typeLocalhostlocalhostProfileprofiles/audit.json

当类型为 Localhost 时必须同时设置 localhostProfile,且路径相对于节点 seccomp 根目录。

添加 AppArmor 配置文件

cks-sys 中创建 Pod apparmor-app(容器名称 app,镜像 nginx:1.27-alpine),并应用 AppArmor 配置文件 k8s-custom-profile。容器 securityContext.appArmorProfiletypeLocalhostlocalhostProfilek8s-custom-profile。(使用旧格式注解 container.apparmor.security.beta.kubernetes.io/app: localhost/k8s-custom-profile 也可通过。)

从 1.30 起使用容器 securityContext.appArmorProfile 字段。旧集群则使用 container.apparmor.security.beta.kubernetes.io/<컨테이너이름> 注解格式,二者任选其一。

丢弃全部 capability 后只恢复一个

cks-sys 中创建 Pod capped(容器名称 app)。容器 securityContext 中 capabilities.drop 只有 ALLcapabilities.add 只有 NET_BIND_SERVICEallowPrivilegeEscalationfalse

drop 中加入 ALL,在 add 中只填写必需项。capability 名称不添加 CAP_ 前缀。

阻止使用主机命名空间

cks-sys 中创建 Pod no-host-ns。显式将 hostNetworkhostPIDhostIPCshareProcessNamespace 全部设为 false

hostNetworkhostPIDhostIPCshareProcessNamespace 都是 Pod 规范顶层字段。四项都写为 false;重新读取保存对象时,前三项会消失,仅 shareProcessNamespace 保留——原因见指示第 5 步。

检测 privileged 容器

cks-sys 中创建 Pod legacy-agent(容器名称 agent),并将容器 securityContext 的 privileged 设为 true(复现旧工作负载)。 然后在 /root/cks-system-hardening/find-privileged.sh 中编写检测脚本, 将执行结果保存到 /root/cks-system-hardening/privileged.txt。结果应列出 cks-sys 命名空间中包含 privileged 容器(包括 init 容器)的 Pod, 格式为 cks-sys/<파드이름>,按字典序排序,每行一个。

使用 jq 遍历 kubectl get pods -o json 时,也要检查 initContainers。输出格式为 네임스페이스/파드이름

综合:只读根文件系统

cks-sys 中创建 Pod hardened(容器名称 app)。容器 securityContext 同时设置 readOnlyRootFilesystem: truerunAsNonRoot: truerunAsUser: 1000allowPrivilegeEscalation: falsecapabilities.drop: [ALL]; Pod 层级 seccompProfile.typeRuntimeDefault。将需要写入的 /tmp 挂载为名为 tmp 的 emptyDir 卷。

根文件系统只读后,临时文件路径也会被阻止。请将 emptyDir 卷挂载到该路径,并把前面使用的字段集中到一个 Pod 中。