TT Lab
开始
学习 学习路径 课程

CCA — Cilium 认证助理

上新策略之前,先看清谁会被拦下

在 TT Lab 中继续学习

目标

在真正的 Cilium 1.20.1(enable-policy=default)中,确认策略从什么时候开始阻断什么。通过 endpoint 的应用列、真实请求和 Hubble 判定,一并观察这些内容:按方向的默认拒绝、用 endpoint 审计模式提前试挂、解除审计后的实际阻断、ingress: [{}] 的完全相反含义、enableDefaultDeny=false,以及两种策略落入同一个存储的情形。

为什么重要

给运行中的服务第一次设置 egress 策略的那一刻最危险。如果存在文档中没有记载的依赖(本实验中的 legacy),那次调用会立刻中断。 Cilium 的审计模式会计算判定但不拒绝,并在 flow 中以 AUDITED 留下记录,因此可以先看到谁会被阻断,再强制执行。

不过,审计模式以 endpoint 为单位,所以连已经在强制执行的方向也会放松。另外,同样形状的 YAML 在 Kubernetes NetworkPolicy 和 CiliumNetworkPolicy 中含义也可能不同。应用列显示 Enabled,也不保证默认拒绝已经开启。这类差异只读文档很容易搞混,所以要亲自发送请求来确认。

把集群范围的模式改成 always,会连没有策略的 endpoint(coredns 等)也一并阻断。在开发 VM 上恢复之后,服务要等约 70 秒才能回来,所以本实验不做修改。审计模式也只对一个 endpoint 开启。

环境准备约需 5 分钟。会话结束后,/root/cca-audit 中的文件会消失。

步骤

  1. 用 kubectl apply -f /opt/fixtures/cca-audit/workload.yaml 在 cca-audit 中启动 Pod api、ledger、legacy、frontend、batch(都是 8080 服务器)以及同名的服务。在设置策略之前创建 /root/cca-audit/baseline.json——在 pods 下的五个应用名称中,各自记录 uid(Pod)、endpoint_id、identity(CiliumEndpoint 的 status)、ingress、egress(agent cilium-dbg endpoint list 中 POLICY 列的值),以及最上层的 batch_to_api(从 batch Pod 请求 http://api:8080/ 的 HTTP 状态码字符串,无响应时为“000”)和 recorded_at(记录那一刻的 UTC 时间,date -u +%Y-%m-%dT%H:%M:%SZ)。
  2. 在 cca-audit 中创建 CiliumNetworkPolicy api-ingress——endpointSelector 为 app=api,ingress 一项(fromEndpoints 为 app=frontend,toPorts 为 TCP “8080”),不设置 egress。生效之后,在 /root/cca-audit/direction.json 中记录 frontend_to_api、batch_to_api、api_to_legacy(各请求的 HTTP 状态码字符串,无响应为“000”)和 api_ingress、api_egress(api endpoint 的 POLICY 列的值)、recorded_at(记录那一刻的 UTC 时间)。必须在第 3 步的 egress 策略之前记录。
  3. 在 api endpoint 上开启审计模式:cilium-dbg endpoint config <api 번호> PolicyAuditMode=Enabled(占位符为 api 的编号)。然后应用 CNP api-egress——endpointSelector 为 app=api,egress 两项:(1)toEndpoints 为 app=ledger,TCP “8080”;(2)toEndpoints 为 k8s:io.kubernetes.pod.namespace: kube-system、k8s:k8s-app: kube-dns,端口 “53”,protocol 为 ANY。请求 api→ledger、api→legacy、batch→api 之后,在 agent 内用 hubble observe 读取 cca-audit 的 flow,记录到 /root/cca-audit/audit.json 中——endpoint_id、audit_mode、api_ingress、api_egress(POLICY 列)、api_to_legacy、batch_to_api(状态码字符串)、flows(包含 api→legacy 和 batch→api 的 AUDITED flow 的 compact 输出行列表)。
  4. 关闭 api endpoint 的审计模式(PolicyAuditMode=Disabled)。再次发送相同的请求,并在 /root/cca-audit/enforce.json 中记录 audit_mode、api_ingress、api_egress,以及 api_to_ledger、api_to_legacy、batch_to_api(状态码字符串)和 flows(关闭审计之后 api→legacy 和 batch→api 的 DROPPED flow 的 compact 行列表)。
  5. 在 cca-audit 中创建两个策略——标准 NetworkPolicy np-empty(podSelector 为 app=batch,policyTypes 为 [Ingress],ingress: [{}])和 CiliumNetworkPolicy cnp-empty(endpointSelector 为 app=frontend,ingress: [{}])。从 ledger Pod 向 batch 和 frontend 发送请求,并在 /root/cca-audit/empty.json 中记录 batch_from_ledger、frontend_from_ledger(状态码字符串)、batch_ingress、frontend_ingress(各 endpoint 的 ingress POLICY 列)。
  6. 在 cca-audit 中创建 CNP ledger-observe——endpointSelector 为 app=ledger,enableDefaultDeny: {ingress: false},ingress 一项(fromEndpoints 为 app=api,TCP “8080”)。请求 api→ledger 和 batch→ledger 并读取 flow,在 /root/cca-audit/observe.json 中记录 ledger_ingress(POLICY 列)、api_to_ledger、batch_to_ledger(状态码字符串)、flows(包含两个请求在 ledger 一侧的 INGRESS policy-verdict 行的 compact 行列表)。
  7. 在 agent 中读取 cilium-dbg policy get -o json,为 cca-audit 命名空间的每条规则找出标签 io.cilium.k8s.policy.name 和 io.cilium.k8s.policy.derived-from。在 /root/cca-audit/sources.json 中记录 derived_from(策略名称 → derived-from 值的字典,共五个)和 revision(输出中的 revision 数字)。
  8. 在 /root/cca-audit/report.txt 中写入七行 키=값(占位符依次为键和值)形式的内容——api_audit_mode(当前 api 的 PolicyAuditMode)、api_policy_columns(api 的 ingress 列/egress 列,例如 A/B 格式)、api_to_legacy(allowed 或 denied)、np_empty_ingress_rule、cnp_empty_ingress_rule(分别为 allow-all 或 deny-all)、ledger_ingress_column、ledger_default_deny_ingress(ledger-observe 的 enableDefaultDeny.ingress 的值,true 或 false)。所有值必须与当前状态一致。

参考

先记下没有任何策略时的应用状态

用 kubectl apply -f /opt/fixtures/cca-audit/workload.yaml 在 cca-audit 中启动 Pod api、ledger、legacy、frontend、batch(都是 8080 服务器)以及同名的服务。在设置策略之前创建 /root/cca-audit/baseline.json——在 pods 下的五个应用名称中,各自记录 uid(Pod)、endpoint_id、identity(CiliumEndpoint 的 status)、ingress、egress(agent cilium-dbg endpoint list 中 POLICY 列的值),以及最上层的 batch_to_api(从 batch Pod 请求 http://api:8080/ 的 HTTP 状态码字符串,无响应时为“000”)和 recorded_at(记录那一刻的 UTC 时间,date -u +%Y-%m-%dT%H:%M:%SZ)。

如果 cilium-config 的 enable-policy 为 default,没有被任何策略选中的 endpoint 不会被阻断。endpoint list 的两个 POLICY 列显示各方向的默认拒绝是否已开启。Pod 里没有 curl,所以请用 python3 的 urllib 发送请求。这份记录必须在后面的策略出现之前生成——评分器会把 recorded_at 与策略的创建时间比较(不看文件修改时间,所以事后修改保存也可以)。

只锁了入站一侧,出站一侧原样不动

在 cca-audit 中创建 CiliumNetworkPolicy api-ingress——endpointSelector 为 app=api,ingress 一项(fromEndpoints 为 app=frontend,toPorts 为 TCP “8080”),不设置 egress。生效之后,在 /root/cca-audit/direction.json 中记录 frontend_to_api、batch_to_api、api_to_legacy(各请求的 HTTP 状态码字符串,无响应为“000”)和 api_ingress、api_egress(api endpoint 的 POLICY 列的值)、recorded_at(记录那一刻的 UTC 时间)。必须在第 3 步的 egress 策略之前记录。

在 default 模式下,默认拒绝是按方向开启的。只要某条规则含有 ingress 部分并选中了 endpoint,只有该方向会变成允许列表方式。被阻断的请求不是被拒绝,而是没有响应,所以要设置较短的超时。生效可能要几秒,所以用轮询更稳妥。

先以审计模式挂上新的 egress 策略,看看谁会被阻断

在 api endpoint 上开启审计模式:cilium-dbg endpoint config <api 번호> PolicyAuditMode=Enabled(占位符为 api 的编号)。然后应用 CNP api-egress——endpointSelector 为 app=api,egress 两项:(1)toEndpoints 为 app=ledger,TCP “8080”;(2)toEndpoints 为 k8s:io.kubernetes.pod.namespace: kube-system、k8s:k8s-app: kube-dns,端口 “53”,protocol 为 ANY。请求 api→ledger、api→legacy、batch→api 之后,在 agent 内用 hubble observe 读取 cca-audit 的 flow,记录到 /root/cca-audit/audit.json 中——endpoint_id、audit_mode、api_ingress、api_egress(POLICY 列)、api_to_legacy、batch_to_api(状态码字符串)、flows(包含 api→legacy 和 batch→api 的 AUDITED flow 的 compact 输出行列表)。

审计模式会计算策略判定,但不拒绝而是放行,并在 flow 中以 AUDITED 留下记录。它是 endpoint 级别的选项,会作用于该 endpoint 的所有方向,请与第 2 步的结果比较这一点。环形缓冲区几分钟就会被新数据挤掉,所以 flow 要在观测后立刻保存为文件;给 --since 传入请求之前的时间点,就只会收集到需要的行。别忘了,锁住 egress 后,名称解析也属于 egress 流量。

解除审计后,同样的两个请求真的被丢弃了

关闭 api endpoint 的审计模式(PolicyAuditMode=Disabled)。再次发送相同的请求,并在 /root/cca-audit/enforce.json 中记录 audit_mode、api_ingress、api_egress,以及 api_to_ledger、api_to_legacy、batch_to_api(状态码字符串)和 flows(关闭审计之后 api→legacy 和 batch→api 的 DROPPED flow 的 compact 行列表)。

请确认审计模式下显示的 AUDITED 是否原样变成了 DROPPED,被允许的目的地(ledger)是否仍然能通过。flow 的末尾部分是判定原因。为了不与前面步骤遗留的旧 DROPPED 行混在一起,请从关闭审计之前的时间点开始读取。

写的都是 ingress: [{}],一边放开了,一边却关闭了

在 cca-audit 中创建两个策略——标准 NetworkPolicy np-empty(podSelector 为 app=batch,policyTypes 为 [Ingress],ingress: [{}])和 CiliumNetworkPolicy cnp-empty(endpointSelector 为 app=frontend,ingress: [{}])。从 ledger Pod 向 batch 和 frontend 发送请求,并在 /root/cca-audit/empty.json 中记录 batch_from_ledger、frontend_from_ledger(状态码字符串)、batch_ingress、frontend_ingress(各 endpoint 的 ingress POLICY 列)。

两种 API 对空的规则项解释不同。在 Kubernetes NetworkPolicy 中,没有 from 的 ingress 项表示所有来源。在 Cilium 中,没有 fromEndpoints 之类来源字段的规则项不允许任何对象,只会开启默认拒绝。还请确认二者的应用列看起来是一样的。

应用列是 Enabled,却没有任何东西被阻断

在 cca-audit 中创建 CNP ledger-observe——endpointSelector 为 app=ledger,enableDefaultDeny: {ingress: false},ingress 一项(fromEndpoints 为 app=api,TCP “8080”)。请求 api→ledger 和 batch→ledger 并读取 flow,在 /root/cca-audit/observe.json 中记录 ledger_ingress(POLICY 列)、api_to_ledger、batch_to_ledger(状态码字符串)、flows(包含两个请求在 ledger 一侧的 INGRESS policy-verdict 行的 compact 行列表)。

关闭了 enableDefaultDeny 的规则,在决定 endpoint 的默认模式时会被排除。所以规则会被计算,却不会导致拒绝。请比较 policy-verdict 行中紧跟在 policy-verdict: 之后的匹配类型,在两个请求中有什么不同。这就是为什么不能仅凭 POLICY 列来判断是否默认拒绝。

两种策略进入同一个存储

在 agent 中读取 cilium-dbg policy get -o json,为 cca-audit 命名空间的每条规则找出标签 io.cilium.k8s.policy.name 和 io.cilium.k8s.policy.derived-from。在 /root/cca-audit/sources.json 中记录 derived_from(策略名称 → derived-from 值的字典,共五个)和 revision(输出中的 revision 数字)。

Cilium 也会把标准 NetworkPolicy 转换成自己的规则格式,放入与 CNP 相同的存储,并用标签标明来源。请查看每条规则的 Labels 列表中的 key/value。每当存储发生变化,revision 就会增加。

应用模式事故报告

在 /root/cca-audit/report.txt 中写入七行 키=값(占位符依次为键和值)形式的内容——api_audit_mode(当前 api 的 PolicyAuditMode)、api_policy_columns(api 的 ingress 列/egress 列,例如 A/B 格式)、api_to_legacy(allowed 或 denied)、np_empty_ingress_rule、cnp_empty_ingress_rule(分别为 allow-all 或 deny-all)、ledger_ingress_column、ledger_default_deny_ingress(ledger-observe 的 enableDefaultDeny.ingress 的值,true 或 false)。所有值必须与当前状态一致。

不要照搬前面步骤的记录,而是重新读取当前状态。评分器也会重新发送请求来判定。如果审计模式又被开启了,报告中的多个值就会不同。