标签规则被放宽了,却没有人发现
目标
用 Kyverno CLI 的 apply、test、jp,在把策略部署到集群之前就判定它会拒绝什么、修改什么、生成什么,
并把这些判定固化为测试套件和 CI 脚本。
为什么重要
准入策略出错时是悄无声息的。把验证规则改松的失误,只是什么请求都不拦,却不会报任何错误;
mutate 规则中的一个锚点,会改变它是否去动已经有值的资源。等到在生产集群里才发现这种差别,就已经晚了。
kyverno test 会先写下“这个资源通过、那个资源失败、这个资源会被这样改”的预期,再与引擎的实际判定比较。
这样,当修改策略的人无意中放宽了规则时,测试会最先亮红灯。集群中不存在的 ConfigMap 或 API 调用结果,
用 values 文件补上,条件表达式的 JMESPath 用 kyverno jp 单独运行,像对待代码一样验证策略仓库。
步骤
- 在
/root/kca-cli/validate/policy.yaml中编写 ClusterPolicyrequire-team。规则check-team选中 Pod,要求metadata.labels.team必须是非空值,failureAction 为Enforce。在/root/kca-cli/validate/resources.yaml中放入 Podgood(标签 team=pay)、没有标签的 Podbad,以及 Pod 模板中没有 team 标签的 Deploymentweb(均在 namespace default 中)。运行kyverno apply并加上--policy-report,把标准输出保存到/root/kca-cli/apply-report.yaml。 - 在
/root/kca-cli/validate/kyverno-test.yaml中编写 Test(apiVersioncli.kyverno.io/v1alpha1)。policies 为policy.yaml,resources 为resources.yaml,results 中期望good为 pass,bad为 fail,Deploymentweb以第 1 步报告中出现的自动生成规则名称为 fail。kyverno test /root/kca-cli/validate必须让 3 条全部通过。 - 把
/root/kca-cli/validate整个复制到/root/kca-cli/regressed,在副本的 policy.yaml 中只把team键改成相等锚点=(team)(标签不存在时反而会通过的常见错误)。把kyverno test /root/kca-cli/regressed --remove-color的完整输出保存到/root/kca-cli/regression.txt,并确认退出码。不要只看汇总行和退出码,而要在表格的 RESULT、REASON 列中找出预期与实际不一致的行。原始的 validate 必须仍然通过。 - 在
/root/kca-cli/mutate/中放入 ClusterPolicyadd-managed-by(规则add-label,在 Pod 上添加managed-by: kyverno标签,但只在该标签不存在时添加,使用追加锚点)、resources.yaml(没有标签的 Podplain,带有managed-by: helm标签的 Podowned)、mutate 之后的预期样子patched.yaml(plain 被加上标签的 Pod)以及 kyverno-test.yaml。测试要把 plain 与 patchedResources 比较并期望 pass,owned 期望 skip,kyverno test /root/kca-cli/mutate必须通过。 - 在
/root/kca-cli/generate/中放入 ClusterPolicyns-quota(规则gen-quota,出现 Namespace 时,在该命名空间中以spec.hard.pods: "10"生成 ResourceQuotadefault-quota,synchronize false)、resources.yaml(Namespaceteam-a)、预期生成物generated.yaml,以及 kyverno-test.yaml(针对 team-a 比较 generatedResource,pass),并让kyverno test /root/kca-cli/generate通过。 - 在
/root/kca-cli/context/中放入 ClusterPolicyallowed-registries。规则check-registry针对 Pod,以上下文reg读取 ConfigMapplatform/registry-config,并用 foreach 遍历所有容器镜像,只要有镜像不符合reg.data.allowed(以逗号分隔的模式列表)中的任何一个模式,就拒绝(Enforce)。resources.yaml 中放入只有一个镜像registry.lab/web:1.0的 Podinternal,以及在其基础上再加一个镜像为docker.io/busybox:1.36的容器sidecar的 Podexternal。由于没有集群,用 values.yaml(kindValues)把reg.data.allowed填充为registry.lab/*,再通过 kyverno-test.yaml 的variables关联起来,让 internal pass、external fail 通过测试。 - 在
/root/kca-cli/jp/query.txt中写一个 JMESPath 表达式。它以 Pod 对象为输入,必须返回镜像不以registry.lab/开头的容器的名称列表。在/root/kca-cli/jp/external.yaml中只放入第 6 步的 Podexternal,并把kyverno jp query -i jp/external.yaml -q jp/query.txt的输出保存到/root/kca-cli/jp/result.json。 - 把
/root/kca-cli/ci.sh做成可执行脚本。以第一个参数接收根目录(没有则用/root/kca-cli),在其下的validate、mutate、generate、context四个目录中各运行一次kyverno test,只要有一个失败,就必须以非 0 的值退出。如第 3 步所见,即使退出码为 0,只要输出表中有预期与实际不一致的行(REASON 为Want ...),也必须按失败处理。不要运行regressed。评分器会用多种方式破坏副本中的策略和测试,检查脚本是否失败。
参考
- 这个镜像中带有 kyverno CLI 1.13.2。用
kyverno version确认。本实验不使用集群。 - 只看测试结果用
kyverno test <디렉터리> --fail-only(占位符为目录),详细原因用--detailed-results。 - 常见错误:把针对 Deployment 的预期写成 Pod 规则的名称。自动生成规则的名称不同。
- 常见错误:只相信
Test Summary行和退出码。这个版本只在表格中保留 fail 预期被翻转为 pass 的行(第 3 步中会亲自确认)。 - 常见错误:用反引号包住 JMESPath 字符串。反引号里是 JSON,没有引号的文字会造成语法错误。
- Kyverno CLI test · Kyverno CLI apply · Kyverno CLI jp · 自动生成规则
在部署策略之前,先问问引擎
在 /root/kca-cli/validate/policy.yaml 中编写 ClusterPolicy require-team。规则 check-team 选中 Pod,要求 metadata.labels.team 必须是非空值,failureAction 为 Enforce。在 /root/kca-cli/validate/resources.yaml 中放入 Pod good(标签 team=pay)、没有标签的 Pod bad,以及 Pod 模板中没有 team 标签的 Deployment web(均在 namespace default 中)。运行 kyverno apply 并加上 --policy-report,把标准输出保存到 /root/kca-cli/apply-report.yaml。
格式为 kyverno apply <정책> --resource <리소스>(占位符依次为策略、资源)。到报告中确认针对 Pod 的规则是否也适用于 Deployment,如果适用,规则名称被报告成什么。有违反时退出码不是 0。
先写下预期结果的测试
在 /root/kca-cli/validate/kyverno-test.yaml 中编写 Test(apiVersion cli.kyverno.io/v1alpha1)。policies 为 policy.yaml,resources 为 resources.yaml,results 中期望 good 为 pass,bad 为 fail,Deployment web 以第 1 步报告中出现的自动生成规则名称为 fail。kyverno test /root/kca-cli/validate 必须让 3 条全部通过。
results 的每个条目都要写 policy、rule、resources、kind、result。由 Pod 规则自动生成的规则,名称前面会带一个前缀。
放宽了标签规则,测试最先发现了
把 /root/kca-cli/validate 整个复制到 /root/kca-cli/regressed,在副本的 policy.yaml 中只把 team 键改成相等锚点 =(team)(标签不存在时反而会通过的常见错误)。把 kyverno test /root/kca-cli/regressed --remove-color 的完整输出保存到 /root/kca-cli/regression.txt,并确认退出码。不要只看汇总行和退出码,而要在表格的 RESULT、REASON 列中找出预期与实际不一致的行。原始的 validate 必须仍然通过。
=(key) 只在键存在时才检查值。看看哪些资源有标签映射却没有 team。这个镜像中的 CLI(1.13.2)在预期为 fail 的资源被判定为 pass 时,虽然会在 REASON 中写下这种不一致,汇总和退出码却仍然显示通过(实测)。测试工具是否把所有不一致都计为失败,只有像这样故意弄坏一次才知道。
连变更后的结果也要比较的 mutate 测试
在 /root/kca-cli/mutate/ 中放入 ClusterPolicy add-managed-by(规则 add-label,在 Pod 上添加 managed-by: kyverno 标签,但只在该标签不存在时添加,使用追加锚点)、resources.yaml(没有标签的 Pod plain,带有 managed-by: helm 标签的 Pod owned)、mutate 之后的预期样子 patched.yaml(plain 被加上标签的 Pod)以及 kyverno-test.yaml。测试要把 plain 与 patchedResources 比较并期望 pass,owned 期望 skip,kyverno test /root/kca-cli/mutate 必须通过。
+(key): value 只在键不存在时才写入。对已经有标签的资源,规则什么都不会改,所以结果可能不是 pass。patchedResources 是文件名。
提前比较将要生成的对象的 generate 测试
在 /root/kca-cli/generate/ 中放入 ClusterPolicy ns-quota(规则 gen-quota,出现 Namespace 时,在该命名空间中以 spec.hard.pods: "10" 生成 ResourceQuota default-quota,synchronize false)、resources.yaml(Namespace team-a)、预期生成物 generated.yaml,以及 kyverno-test.yaml(针对 team-a 比较 generatedResource,pass),并让 kyverno test /root/kca-cli/generate 通过。
要生成的对象的 namespace 使用请求对象的名称作为变量。预期生成物中必须包含 metadata.namespace,比较才能对上。
没有集群也能填充 ConfigMap 上下文
在 /root/kca-cli/context/ 中放入 ClusterPolicy allowed-registries。规则 check-registry 针对 Pod,以上下文 reg 读取 ConfigMap platform/registry-config,并用 foreach 遍历所有容器镜像,只要有镜像不符合 reg.data.allowed(以逗号分隔的模式列表)中的任何一个模式,就拒绝(Enforce)。resources.yaml 中放入只有一个镜像 registry.lab/web:1.0 的 Pod internal,以及在其基础上再加一个镜像为 docker.io/busybox:1.36 的容器 sidecar 的 Pod external。由于没有集群,用 values.yaml(kind Values)把 reg.data.allowed 填充为 registry.lab/*,再通过 kyverno-test.yaml 的 variables 关联起来,让 internal pass、external fail 通过测试。
CLI 无法查询上下文中的 configMap,所以通过规则级的 values 提供变量值。有把逗号分隔的字符串转成列表的 JMESPath 函数。AnyNotIn 能理解值列表中的通配符。
先在策略之外运行条件表达式
在 /root/kca-cli/jp/query.txt 中写一个 JMESPath 表达式。它以 Pod 对象为输入,必须返回镜像不以 registry.lab/ 开头的容器的名称列表。在 /root/kca-cli/jp/external.yaml 中只放入第 6 步的 Pod external,并把 kyverno jp query -i jp/external.yaml -q jp/query.txt 的输出保存到 /root/kca-cli/jp/result.json。
使用过滤表达式 [?조건](占位符为条件)和字符串函数 starts_with。JMESPath 中字符串字面量用单引号(反引号是 JSON 字面量)。评分器也会用其他 Pod 来运行表达式。
策略仓库的 CI 守门人
把 /root/kca-cli/ci.sh 做成可执行脚本。以第一个参数接收根目录(没有则用 /root/kca-cli),在其下的 validate、mutate、generate、context 四个目录中各运行一次 kyverno test,只要有一个失败,就必须以非 0 的值退出。如第 3 步所见,即使退出码为 0,只要输出表中有预期与实际不一致的行(REASON 为 Want ...),也必须按失败处理。不要运行 regressed。评分器会用多种方式破坏副本中的策略和测试,检查脚本是否失败。
把输出放进变量或文件,同时检查退出码和不一致的字样。无论是在循环中遇到第一个失败就结束,还是全部运行完再汇总后结束,只要结果码正确即可。混入颜色码会让字符串搜索落空,所以要使用 --remove-color。