LabHub
学习 学习路径 课程

CKS — Kubernetes 安全专家

被挡住的时机不同,要修的地方也不同

在 LabHub 中继续学习

一句话总结

练习编写配置,与练习确认配置确实得到执行,是两回事。事故总是发生在后者。

概念图: 即使违规的 Pod 也会直接变成 Running · 如何确认配置确实生效 · 准入 · kubelet

为什么必须在真正强制执行的环境中练习

前面的模块多次使用了 securityContext 和 Pod Security。然而,运行那些实验的环境既没有 kubelet,也没有容器运行时,所以即使违规的 Pod 也会直接变成 Running

这让我们练习了如何编写配置,却没有练习如何确认配置确实生效。而实际工作中的事故恰恰发生在后者——配置看似全都写好了,实际却没有应用,而这种状态仍然显得一切正常。

同一个“不可行”分散在三个层面

层面 这里有什么 症状 何时出现
准入 Pod Security Admission、策略引擎 kubectl apply 被拒绝 部署前(CI)
kubelet runAsNonRoot、镜像 USER 检查 Pod 已创建,出现 CreateContainerConfigError 刚部署后
运行时 只读根文件系统、capability、seccomp Pod 为 Running,只有应用失败 走到相应代码路径时

最好的是准入。反馈立即到达,而且错误资源根本不会进入集群。

最糟的是运行时。Pod 看起来运行正常,症状因此像应用错误。平时一切正常、只在特定代码路径失败的情况也很常见。

所以应遵循这个顺序:能在前一层阻止的问题,不要留给后一层。

Pod Security Admission 要从 warn 开始

如果直接启用 enforce,现有工作负载会在下一次重新部署时全部被阻止。这本身就是故障。

pod-security.kubernetes.io/warn: restricted     # 먼저 이것만
pod-security.kubernetes.io/enforce: restricted  # 경고가 0 이 된 뒤에

顺序不是先切断再统计,而是先统计再切断。

收紧容器后会破坏什么

提前了解启用安全设置后实际会在哪里失败,便能在故障发生前修正镜像。下面只整理最常遇到的情况。

只读根文件系统。 大多数应用都会在某处写临时文件,包括日志、缓存、套接字,以及语言运行时使用的临时目录。把根目录设为只读后,这些写入全部会失败,但症状通常表现为应用权限错误,很难立刻联想到安全设置。解决办法是为每个需要写入的路径挂载 emptyDir与其猜测应用写入哪里,不如先在收紧后的状态运行一次,收集所有失败路径,这样更快。

以非 root 用户运行。 首先遇到的是无法监听 1024 以下端口。原本在容器内监听 80 端口的镜像必须改为 8080,同时修改 Service 的 targetPort。此外,如果镜像内文件归 root 所有,新用户可能无法读取,因此构建镜像时也要一并转移所有权。

删除全部 capability。 大多数应用一个都不需要,但监听低端口需要 NET_BIND_SERVICE,使用 ping 需要 NET_RAW。应当按照只加回确实需要的能力这一顺序操作;如果在不知道需要什么的情况下保留全部能力,收紧就失去了意义。

seccomp 默认配置。 不被允许的系统调用会返回 EPERM;如果库把这个错误吞掉,症状就会出现在看似毫不相关的地方。本实验环境中某些容器实验受阻,也是同类原因。

总之,顺序应当是:先以收紧后的配置运行,收集失败项,只为这些失败开放例外,再逐步从镜像中消除例外清单。 反过来,如果从“先全部放开,以后再收紧”开始,那个“以后”永远不会到来。

实务中真正重要的事

能在前一层阻止的问题,不要留给后一层。 在准入阶段拦截,CI 会立即发现;在运行时才触发,Pod 会看似正常地运行,直到特定代码路径失败,症状就像应用错误。同一条规则由哪一层强制执行,会让响应成本相差十倍。

等警告归零后再启用 enforce 直接启用会让现有工作负载在下次部署时全部受阻,这本身就是故障。必须遵循先用 warn 统计、再实施阻断的顺序。

runAsNonRoot 失败看起来并不像 Pod 创建失败。 Pod 会成功创建,只留下 CreateContainerConfigError,因此往往很久以后才会怀疑镜像中的 USER。在构建镜像时明确指定 USER,是成本最低的预防方式。

下一个实验将在真实集群上逐项验证这三点。