用 seccomp、AppArmor、capability 收紧
目标
掌握仅通过 Pod 规范减少容器可向内核请求的操作,并建立检测集群中遗留 privileged 容器的流程。
为什么重要
容器共享主机内核。逃逸漏洞大多通过系统调用或 capability 攻击内核。因此,防御不应只着眼于阻止入侵,而应预先减少入侵进程能够执行的操作。seccomp 限制可调用的系统调用,AppArmor 限制可访问的文件与功能,capabilities 限制 root 权限的各个部分。三层相互叠加,即使一层被突破,其他层仍在。反之,privileged: true 会一次性使三层失效,因此统计生产集群中的 privileged 容器就是在衡量风险。
注意:此实验环境没有真实容器运行时,无法确认 seccomp、AppArmor 配置文件是否真正强制执行,也不检查节点上是否存在配置文件。评分只检查 Pod 规范字段是否准确。CKS 考试最终检查的也是这些规范。
步骤
-
创建命名空间
cks-sys,并在其中创建 Podseccomp-default(镜像nginx:1.27-alpine)。Pod 层级spec.securityContext.seccompProfile.type为RuntimeDefault。 -
在
cks-sys中创建 Podseccomp-custom。Pod 层级 seccompProfile 的type为Localhost,localhostProfile为profiles/audit.json。 -
在
cks-sys中创建 Podapparmor-app(容器名称app,镜像nginx:1.27-alpine),并应用 AppArmor 配置文件k8s-custom-profile。容器securityContext.appArmorProfile的type为Localhost,localhostProfile为k8s-custom-profile。(使用旧格式注解container.apparmor.security.beta.kubernetes.io/app: localhost/k8s-custom-profile也可通过。) -
在
cks-sys中创建 Podcapped(容器名称app)。容器 securityContext 中capabilities.drop只有ALL,capabilities.add只有NET_BIND_SERVICE,allowPrivilegeEscalation为false。 -
在
cks-sys中创建 Podno-host-ns。显式将hostNetwork、hostPID、hostIPC、shareProcessNamespace全部设为false。这里隐藏着审计必须了解的事实。PodSpec 中前三个字段是
bool+omitempty,即使写为false,序列化时也会完全消失。因此,仅查看保存的对象无法区分“显式写了 false”和“完全没写”。真正的审计标准不是“是否写了 false”,而是是否不为 true。而shareProcessNamespace是*bool,所以false会保留——评分也反映了这一区别。应用后用kubectl get pod no-host-ns -n cks-sys -o yaml亲自确认。 -
在
cks-sys中创建 Podlegacy-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/<파드이름>,按字典序排序,每行一个。 -
在
cks-sys中创建 Podhardened(容器名称app)。容器 securityContext 同时设置readOnlyRootFilesystem: true、runAsNonRoot: true、runAsUser: 1000、allowPrivilegeEscalation: false、capabilities.drop: [ALL];Pod 层级seccompProfile.type为RuntimeDefault。将需要写入的/tmp挂载为名为tmp的 emptyDir 卷。
参考
- 使用
kubectl run seccomp-default --image=nginx:1.27-alpine -n cks-sys --dry-run=client -o yaml > pod.yaml生成草稿后编辑更快。 - 使用
kubectl explain pod.spec.securityContext.seccompProfile查看字段名称。 - 检测脚本提示:在
kubectl get pods -n cks-sys -o json | jq -r '...'中同时遍历.spec.containers[]和.spec.initContainers[]。 - 常见错误 1:把
capabilities放在 Pod 层级 securityContext。capabilities 仅用于容器层级。 - 常见错误 2:
localhostProfile使用绝对路径。它必须是相对于节点 seccomp 根目录的路径。
运行时默认 seccomp 配置文件
创建命名空间 cks-sys,并在其中创建 Pod seccomp-default(镜像
nginx:1.27-alpine)。Pod 层级 spec.securityContext.seccompProfile.type 为
RuntimeDefault。
seccompProfile 可用于 Pod 层级 spec.securityContext 或容器层级。本步骤使用 Pod 层级。
指定自定义 seccomp 配置文件
在 cks-sys 中创建 Pod seccomp-custom。Pod 层级 seccompProfile 的 type 为
Localhost,localhostProfile 为 profiles/audit.json。
当类型为 Localhost 时必须同时设置 localhostProfile,且路径相对于节点 seccomp 根目录。
添加 AppArmor 配置文件
在 cks-sys 中创建 Pod apparmor-app(容器名称 app,镜像 nginx:1.27-alpine),并应用
AppArmor 配置文件 k8s-custom-profile。容器
securityContext.appArmorProfile 的 type 为 Localhost,localhostProfile 为
k8s-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 只有 ALL,capabilities.add 只有 NET_BIND_SERVICE,
allowPrivilegeEscalation 为 false。
在 drop 中加入 ALL,在 add 中只填写必需项。capability 名称不添加 CAP_ 前缀。
阻止使用主机命名空间
在 cks-sys 中创建 Pod no-host-ns。显式将 hostNetwork、hostPID、hostIPC、
shareProcessNamespace 全部设为 false。
hostNetwork、hostPID、hostIPC、shareProcessNamespace 都是 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: true、runAsNonRoot: true、runAsUser: 1000、
allowPrivilegeEscalation: false、capabilities.drop: [ALL];
Pod 层级 seccompProfile.type 为 RuntimeDefault。将需要写入的 /tmp
挂载为名为 tmp 的 emptyDir 卷。
根文件系统只读后,临时文件路径也会被阻止。请将 emptyDir 卷挂载到该路径,并把前面使用的字段集中到一个 Pod 中。