四个锚点,以及不拆策略也能渐进落地的办法
一句话总结
validate 规则有三种语法:照着对象的形状描绘的 pattern、用条件表达式拒绝的 deny.conditions,以及 CEL 表达式。再加上按规则单位设定强制级别的 failureAction,就可以不必把策略拆成两个,而只把新规则以观察模式叠加上去。
为什么需要它
Kyverno 的出发点是不让人再学一门新的策略语言。OPA/Gatekeeper 要学 Rego,还要把 ConstraintTemplate 和 Constraint 两个对象配成一对。Kyverno 则是把要检查的对象原样用 YAML 描绘出来,在值的位置放上运算符。如果 Pod 的容器必须有内存限制,就按 Pod 规格的形状来写,在值的位置放上表示“非空”的运算符。
但是,照着形状描绘的方式有局限。“如果有这个字段,也要看那个条件”或“这个键根本不能存在”这类关系,无法用值来表达。于是出现了附加在键名上的锚点。
工作原理
值位置的运算符如下。?* 表示非空值,* 表示包括 null 在内的所有值,X|Y 表示二者之一,!X 表示不是 X 的值,而 >=256Mi 这样可以做数值比较。
键位置的锚点有四种,这里最容易出事故。
| 锚点 | 名称 | 含义 |
|---|---|---|
() |
条件 | 仅当该键与该值匹配时,才检查其余部分 |
=() |
相等 | 如果该键存在,值必须满足条件 |
^() |
存在 | 数组中至少要有一个满足条件的元素 |
X() |
否定 | 该键不能存在 |
最常见的误用是否定锚点。很多人把 X(privileged) 理解为“privileged 的值必须不同”,但准确含义是“不能存在名为 privileged 的键本身”。即使值明确写成 false,只要键存在就会被拦下。想比较值,就不要用锚点,而要用值运算符或 deny 条件。
foreach 会检查集合中的每个元素。这里也有一个陷阱:foreach.list 接收的就是 JMESPath 表达式本身,所以不能用花括号包起来。还有,需要连 initContainers 一起检查时使用的惯用写法是 request.object.spec.[initContainers, containers][]。漏掉 initContainers 的策略其实非常常见,绕过策略的不是攻击者,而是使用初始化容器的普通团队。
deny.conditions 在条件为真时拒绝。用 all 和 any 组合,每个条件由 key、operator、value 三个部分构成。pattern 表达起来别扭的情况,大多会落到这里。CEL 是 Kubernetes 1.25 之后成为标准的表达式语言,在 Kyverno 中也以 validate.cel 的形式提供。不过它是已经在运维 Kyverno 时有用的选项,并不构成引入 Kyverno 的理由。如果只是检查一个字段,Kubernetes 内置的 ValidatingAdmissionPolicy 就够了,少运维一个组件,意味着升级对象少了一个,也少了一件要操心 Webhook 证书续期的事。
最后是强制级别。以前由 spec.validationFailureAction 决定整个策略的强制级别。所以在一个策略中,如果有的规则有把握想要阻止,而另一些规则还只想观察,就必须把策略拆成两个,match 块被复制,需要管理的对象也就变多了。现在它下移到了 spec.rules[*].validate[*].failureAction,值有 Enforce 和 Audit 两种。不指定时默认为 Audit。把新规则以 Audit 叠加到现有策略上,看几天报告,再只把那条规则升到 Enforce,这种流程在不拆分策略的情况下成为可能,这就是这项变更的价值。与它一同变动的字段有 webhookTimeoutSeconds 和 failurePolicy,分别迁移到 webhookConfiguration.timeoutSeconds、webhookConfiguration.failurePolicy,从 1.13 起被标记为已弃用(deprecated)。
在现场相遇的样子
作者的镜像扫描经验与这个主题完全吻合。某个镜像第一次扫描时共出现 1,247 条,其中 Critical 有 9 条。如果把这份报告原样贴到团队频道,结果只有两种:所有人都无视它,或者没人能动手,发布被卡住。加上一个只保留已有修复版本的选项后变成了 4 条,再与已确认被实际利用的列表对照,今天需要处理的只有 1 条。
策略也是一样。一开始就全面 Enforce,部署会整体被阻止,最后成为瓶颈的不是策略,而是制定策略的人。可如果全部设为 Audit,又没人看报告。答案是规则单位的 failureAction。只把确定的几条规则升到 Enforce,其余设为 Audit 观察,那么摆在人面前的就只剩现在可以处理的项目。这与从扫描报告中学到的原理相同。
下一项实验要做什么
在 /root/kca-validate/ 中编写 ClusterPolicy,把 match、pattern、deny、exclude 一块一块填上,并给每条规则设置不同的 failureAction。然后把同样的规则用 Kubernetes 内置的 ValidatingAdmissionPolicy 和 Binding 实际部署到集群上,亲眼比较在没有策略引擎的情况下能做到什么程度。