亲眼看策略真的挡住了什么
本实验在真正的 Kubernetes 中进行
VM 内实际运行着一台 k3s,并有强制执行 NetworkPolicy 的控制器。 因此,添加策略后流量会真正被阻断。 评分器也不会只查看文件, 而是每次都实际尝试连接并确认结果。
首次启动大约需要 2 分钟。
目标
逐层构建采用允许列表方式的网络策略,并在此过程中亲手制造、修复一个 实际工作中最常发生的事故。
为什么重要
Kubernetes 的默认行为是全部允许。任何命名空间中的任何 Pod 都能访问 其他所有 Pod。攻击者只要攻陷一个 Web Pod,就能从那里看到数据库。
但策略的工作方式并不直观。如果 Pod 没有应用任何策略,则全部允许; 一旦应用至少一个策略,该 Pod 就会转为允许列表方式。 因此,我们编写的 不是“阻止某项流量的策略”,而是“允许某项流量的策略”。如果不了解这种转换, 添加的策略越多,越容易意外中断其他通信。
最常见的事故出现在第 5 步。默认阻止 egress 的瞬间,DNS 也会一起中断。 因为通往 CoreDNS 的 53 端口同样属于 egress。症状表现为“无法解析名称”, 所以没人会怀疑刚刚添加的网络策略。
步骤
预置内容:shop 命名空间中有 api(app=api)和 db(app=db,nginx)Pod,另外还有 ops 命名空间。如不存在,请在第 1 步创建。
- 确认在没有任何策略时,从任意 Pod 都能访问
db,并保存到/root/k8snp/baseline.txt。必须看到响应码 200。 - 在
shop中创建default-deny策略。其podSelector: {},policyTypes: [Ingress]。然后把现在已被阻止的结果记录到/root/k8snp/deny.txt。 - 使用
allow-api策略,只允许app=apiPod 访问db。将结果保存到/root/k8snp/allow-pod.txt。策略应用于接收方(app=db)。 - 使用
allow-ops策略允许整个ops命名空间,并保存到/root/k8snp/namespace.txt。通过kubernetes.io/metadata.name标签选择命名空间。 - 在
shop中添加deny-egress(policyTypes: [Egress],空 selector),观察发生了什么。然后在/root/k8snp/incident.md中编写事故报告,必须包含原因是 DNS、53 端口以及 CoreDNS 所在的命名空间。 - 使用
allow-dns策略只开放 DNS,使其恢复。将结果保存到/root/k8snp/fixed.txt。53 端口必须同时开放 UDP 和 TCP。 - 为
allow-api添加端口限制(80),并将结果保存到/root/k8snp/ports.txt。 - 在
/root/k8snp/report.md中写两行:policies=(策略数量)和dns_fix_policy=allow-dns,并说明从默认允许切换为允许列表的时点。
参考
- 使用
kubectl -n shop run t --rm -i --restart=Never --image=busybox:1.36 -- timeout 5 wget -q -O- http://<db의 IP>/测试连接。用 Pod IP 代替 Service 名称,可以避免混淆 DNS 问题与策略问题。 kubernetes.io/metadata.name标签由 Kubernetes 自动添加到所有命名空间,无需手动添加。- 第 6 步中如果留空
to,表示“任意目的地”。这样 DNS 虽然恢复,其他 egress 也会全部开放,使第 5 步失去意义。请限制为kube-system命名空间。 - 常见错误 1:把策略应用于发送方。ingress 规则始终选择接收方 Pod。
- 常见错误 2:第 6 步只开放 UDP。DNS 响应超过 512 字节时会切换到 TCP。只开放 UDP 会导致偶发失败,更难定位原因。
- 常见错误 3:在
allow-api中留空ports。这样api可以访问 db 的所有端口。
默认行为是全部允许
确认在没有任何策略时,从任意 Pod 都能访问 db,并保存到 /root/k8snp/baseline.txt。必须看到响应码 200。
创建策略前先测量当前状态。要知道改变了什么,必须先了解改变之前的情况。
只要添加一个策略,方式就会改变
在 shop 中创建 default-deny 策略。其 podSelector: {},policyTypes: [Ingress]。然后把现在已被阻止的结果记录到 /root/k8snp/deny.txt。
podSelector: {} 表示“该命名空间中的所有 Pod”。不提供任何规则,就表示“没有允许的流量”。
将策略应用于接收方
使用 allow-api 策略,只允许 app=api Pod 访问 db。将结果保存到 /root/k8snp/allow-pod.txt。策略应用于接收方(app=db)。
策略的 podSelector 选择受保护的 Pod(db),ingress.from 选择允许的来源(api)。如果把方向写反,将不会产生效果。
按命名空间允许访问
使用 allow-ops 策略允许整个 ops 命名空间,并保存到 /root/k8snp/namespace.txt。通过 kubernetes.io/metadata.name 标签选择命名空间。
namespaceSelector 通过 kubernetes.io/metadata.name 标签进行选择。该标签由 Kubernetes 自动添加到所有命名空间。
阻止 egress 后名称无法解析
在 shop 中添加 deny-egress(policyTypes: [Egress],空 selector),观察发生了什么。然后在 /root/k8snp/incident.md 中编写事故报告,必须包含原因是 DNS、53 端口以及 CoreDNS 所在的命名空间。
默认阻止 egress 后,通往 CoreDNS 的 53 端口也会一起被阻止。在事故报告中写明原因、端口和 CoreDNS 的位置。
只开放 DNS 使其恢复
使用 allow-dns 策略只开放 DNS,使其恢复。将结果保存到 /root/k8snp/fixed.txt。53 端口必须同时开放 UDP 和 TCP。
在 to 中选择 kube-system 命名空间,并在 ports 中同时加入 UDP 53 和 TCP 53。
只选择来源还只完成了一半
为 allow-api 添加端口限制(80),并将结果保存到 /root/k8snp/ports.txt。
在 ingress 条目中添加 ports。如果留空,该来源就能访问目标 Pod 的所有端口。
学到了什么
在 /root/k8snp/report.md 中写两行:policies=(策略数量)和 dns_fix_policy=allow-dns,并说明从默认允许切换为允许列表的时点。
写入 policies= 和 dns_fix_policy= 两行,并说明默认允许何时转为允许列表。