攻击面地图 — 从哪儿进来
一句话总结
Kubernetes 的入口点比想象中少:API server、kubelet、etcd、container registry, 以及 workload 本身。 其余大多只是通往这五者之一的路径。
为什么需要了解这一点
“保护 cluster”之所以含糊,是因为没有列出要保护的对象。 攻击者会带着清单行动——扫描开放端口、尝试匿名访问、计算现有 credential 能够做什么。 防守方也要拥有同样的清单,双方才会对称。
工作原理
五个入口点
1)kube-apiserver(6443)
它是 cluster 唯一的大门,也是最有价值的目标。这里要看三件事:
是否开放匿名访问(--anonymous-auth)、authorization mode 是什么(AlwaysAllow 是灾难),
以及谁拥有什么 credential。
2)kubelet(10250、10255)
它是最常被遗忘的目标。kubelet 有自己的 API,其中包含 Pod 执行、exec 和日志。
如果设置了 --anonymous-auth=true,或 authorization mode 为 AlwaysAllow,那么任何能够通过网络到达 node 的人,
都可以在该 node 的全部 container 中执行命令。read-only port(10255)也不要求认证,
会原样暴露 Pod spec 与环境变量。所以 readOnlyPort=0 是 hardening 标准项目。
3)etcd(2379/2380) 整个 cluster state 都在这里。能读取 etcd 就能读取所有 Secret;能写入 etcd, 就能绕过 apiserver 的全部 authorization check,控制整个 cluster。 默认配置下 Secret 实际上以明文存储,因此获取 etcd backup file、snapshot 或 disk image 的人 也会得到同样的内容。
4)container registry 与 image supply chain
攻击者无需直接接入 cluster,而是让我们主动下载并运行其代码。
tag 会移动——myapp:1.4.2 昨天和今天可能指向不同 image。
如果不固定 digest,就无法确定究竟部署了什么。
5)workload 本身
已经在 cluster 内运行的 Pod 本来就在内部。它有多条向外路径:
使用挂载的 ServiceAccount token 调用 API,通过 hostPath 访问 node filesystem,
以 privileged container 控制 node,或通过 hostNetwork 进入 node 的 network namespace。
经常遗漏的表面
- ServiceAccount token 自动挂载——默认开启,所以每个 Pod 都持有与 API server 对话的凭证。
不需要时,应通过
automountServiceAccountToken: false关闭。 - 没有 audit log——不仅无法阻止攻击,甚至无法知道攻击是否发生过。 如果不记录拒绝事件,试探权限这一信号本身就不存在。
- admission webhook——它是防御手段,同时也是攻击面。webhook 终止后,根据
failurePolicy, 要么所有部署都会被阻止(Fail),要么整个 policy 被绕过(Ignore)。 - CI/CD credential——pipeline 通常拥有向 cluster 部署的权限,相应 token 就是 cluster access 权限。
云原生独有的两种特性
动态性。 Pod 不断终止并重新启动,IP 随之改变。基于 IP 的 firewall rule 会失去意义, 因此需要转向以 label 和 identity(例如 SPIFFE)为基础的 policy。
证据会挥发。 被入侵的 Pod 可能已经消失。如果不把日志和 audit record 发送到 cluster 外部, 就不会留下可供调查的对象。
实际工作中的表现
作者博客总结的失败模式中,从 KCSA 角度看尤其有价值的一项,是 Kubernetes Secret 的本质。
执行 etcdctl get /registry/secrets/default/app-db | hexdump -C,可以看到明文的 key path;
一行 kubectl get secret ... -o jsonpath='{.data.password}' | base64 -d 就能得到密码。
base64 没有 key,只需一条命令便能还原,因此不是加密。
再叠加 RBAC 问题:如果在某个 namespace 中拥有 get secrets 权限,
就能读取该 namespace 的全部 Secret。 用作者的话说,“为了方便而给开发人员的 edit 权限,
实际上常常就是读取 production credential 的权限。”
另一个误解与环境变量有关。加载 .env 文件后再删除也没有用——
内核已经为每个进程保存相应值,可以通过 /proc/PID/environ 读取。
同一 Pod 的 sidecar、node 访问者,以及通过 kubectl debug --target=app 连接的 debug container
都能看到同样的内容。关键在于:环境变量不是存储位置,而是传递方式。
下个测验将检查什么
本模块以测验结束。下一模块会从 apiserver 的 authentication chain 一直深入到 kubelet authorization, 逐组件进行分析;练习中则会亲自编写危险 flag 清单与 EncryptionConfiguration。