Skip to content

Commit 98ba3d9

Browse files
janiszclauderukletsov
authored
Add CEL template (#1012)
Signed-off-by: Tomasz Janiszewski <tomek@redhat.com> Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Alex Rukletsov <alexr@redhat.com>
1 parent b798c78 commit 98ba3d9

File tree

11 files changed

+612
-1
lines changed

11 files changed

+612
-1
lines changed

docs/generated/templates.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ KubeLinter supports the following templates:
6363
type: string
6464
```
6565

66+
## CEL
67+
68+
**Key**: `cel-expression`
69+
70+
**Description**: Flag objects with CEL expression
71+
72+
**Supported Objects**: Any
73+
74+
75+
**Parameters**:
76+
77+
```yaml
78+
- description: 'Check contains a CEL expression for validation logic. Two predefined
79+
variables are available: ''object'' (the current Kubernetes object being processed)
80+
and ''objects'' (all objects being linted).'
81+
name: check
82+
negationAllowed: true
83+
regexAllowed: false
84+
required: true
85+
type: string
86+
```
87+
6688
## cluster-admin Role Binding
6789

6890
**Key**: `cluster-admin-role-binding`

e2etests/bats-tests.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,35 @@ get_value_from() {
1616
echo "${value}"
1717
}
1818

19+
@test "template-cel" {
20+
tmp="tests/checks/cel.yml"
21+
cmd="${KUBE_LINTER_BIN} lint --config e2etests/testdata/cel-config.yaml --do-not-auto-add-defaults --format json ${tmp}"
22+
run ${cmd}
23+
24+
print_info "${status}" "${output}" "${cmd}" "${tmp}"
25+
[ "$status" -eq 1 ]
26+
27+
message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message')
28+
message2=$(get_value_from "${lines[0]}" '.Reports[1].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[1].Diagnostic.Message')
29+
message3=$(get_value_from "${lines[0]}" '.Reports[2].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[2].Diagnostic.Message')
30+
message4=$(get_value_from "${lines[0]}" '.Reports[3].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[3].Diagnostic.Message')
31+
message5=$(get_value_from "${lines[0]}" '.Reports[4].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[4].Diagnostic.Message')
32+
message6=$(get_value_from "${lines[0]}" '.Reports[5].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[5].Diagnostic.Message')
33+
34+
count=$(get_value_from "${lines[0]}" '.Reports | length')
35+
36+
echo $message2
37+
38+
[[ "${message1}" == "Deployment: CEL check expression returned: Object has reloader annotation" ]]
39+
[[ "${message2}" == "ServiceAccount: CEL check expression returned: Invalid EKS IAM role ARN format" ]]
40+
[[ "${message3}" == "ServiceMonitor: CEL check expression returned: no services found matching the service monitor's label selector and namespace selector" ]]
41+
[[ "${message4}" == "ServiceMonitor: CEL check expression returned: no services found matching the service monitor's label selector and namespace selector" ]]
42+
[[ "${message5}" == "ServiceMonitor: CEL check expression returned: no services found matching the service monitor's label selector and namespace selector" ]]
43+
[[ "${message6}" == "ServiceMonitor: CEL check expression returned: no services found matching the service monitor's label selector and namespace selector" ]]
44+
45+
[[ "${count}" == "6" ]]
46+
}
47+
1948
@test "template-check-installed-bash-version" {
2049
run "bash --version"
2150
[[ "${BASH_VERSION:0:1}" -ge '4' ]] || false

e2etests/testdata/cel-config.yaml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
checks:
2+
addAllBuiltIn: false
3+
customChecks:
4+
- name: "cel-forbidden-annotation"
5+
description: "cel espression cehck"
6+
remediation: "Remove foo.bar/baz annotation"
7+
scope:
8+
objectKinds:
9+
- DeploymentLike
10+
template: "cel-expression"
11+
params:
12+
check: |
13+
has(object.metadata.annotations) && object.metadata.annotations["foo.bar/baz"] == "true" ? "Object has reloader annotation" : ""
14+
15+
- name: invalid-irsa-role
16+
description: "IRSA annotations must have a valid IAM Role ARN value"
17+
remediation: "Validate the format of the annotation's value to ensure it is a valid IAM Role ARN"
18+
scope:
19+
objectKinds:
20+
- ServiceAccount
21+
template: "cel-expression"
22+
params:
23+
check: |
24+
object.metadata.annotations["eks.amazonaws.com/role-arn"].matches("^arn:aws:iam::\\d{12}:role/[\\w+=,.@-]{1,64}$") ? "" : "Invalid EKS IAM role ARN format"
25+
26+
- name: "cel-dangling-servicemonitor"
27+
description: "Flag service monitors which do not match any service"
28+
remediation: "Ensure the ServiceMonitor's selector matches at least one Service"
29+
scope:
30+
objectKinds:
31+
- ServiceMonitor
32+
template: "cel-expression"
33+
params:
34+
check: |
35+
// Check if ServiceMonitor has selectors
36+
(!has(object.spec.selector.matchLabels) || size(object.spec.selector.matchLabels) == 0) &&
37+
(!has(object.spec.namespaceSelector.matchNames) || size(object.spec.namespaceSelector.matchNames) == 0) &&
38+
(!has(object.spec.namespaceSelector.any) || !object.spec.namespaceSelector.any) ?
39+
"service monitor has no selector specified" :
40+
41+
// Check if any services match the ServiceMonitor's selectors
42+
!objects.exists(obj,
43+
obj.kind == "Service" &&
44+
(
45+
// Check namespace selector
46+
(has(object.spec.namespaceSelector.any) && object.spec.namespaceSelector.any) ||
47+
(has(object.spec.namespaceSelector.matchNames) &&
48+
object.spec.namespaceSelector.matchNames.exists(ns, ns == (has(obj.metadata.namespace) ? obj.metadata.namespace : ""))) ||
49+
(!has(object.spec.namespaceSelector.matchNames) && !has(object.spec.namespaceSelector.any))
50+
) &&
51+
(
52+
// Check label selector
53+
(!has(object.spec.selector.matchLabels) || size(object.spec.selector.matchLabels) == 0) ||
54+
(has(obj.metadata.labels) &&
55+
object.spec.selector.matchLabels.all(key, key in obj.metadata.labels &&
56+
obj.metadata.labels[key] == object.spec.selector.matchLabels[key]))
57+
)
58+
) ?
59+
"no services found matching the service monitor's label selector and namespace selector" : ""

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/cert-manager/cert-manager v1.18.2
99
github.com/fatih/color v1.18.0
1010
github.com/go-viper/mapstructure/v2 v2.4.0
11+
github.com/google/cel-go v0.26.0
1112
github.com/kedacore/keda/v2 v2.17.2
1213
github.com/mitchellh/go-homedir v1.1.0
1314
github.com/mitchellh/mapstructure v1.5.0
@@ -27,12 +28,14 @@ require (
2728
)
2829

2930
require (
31+
cel.dev/expr v0.24.0 // indirect
3032
dario.cat/mergo v1.0.1 // indirect
3133
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
3234
github.com/BurntSushi/toml v1.5.0 // indirect
3335
github.com/MakeNowJust/heredoc v1.0.0 // indirect
3436
github.com/Masterminds/goutils v1.1.1 // indirect
3537
github.com/Masterminds/semver/v3 v3.3.0 // indirect
38+
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
3639
github.com/beorn7/perks v1.0.1 // indirect
3740
github.com/blang/semver/v4 v4.0.0 // indirect
3841
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -103,6 +106,7 @@ require (
103106
github.com/sourcegraph/conc v0.3.0 // indirect
104107
github.com/spf13/afero v1.12.0 // indirect
105108
github.com/spf13/cast v1.7.1 // indirect
109+
github.com/stoewer/go-strcase v1.3.0 // indirect
106110
github.com/subosito/gotenv v1.6.0 // indirect
107111
github.com/x448/float16 v0.8.4 // indirect
108112
github.com/xlab/treeprint v1.2.0 // indirect
@@ -119,6 +123,7 @@ require (
119123
golang.org/x/text v0.27.0 // indirect
120124
golang.org/x/time v0.11.0 // indirect
121125
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
126+
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
122127
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
123128
google.golang.org/grpc v1.71.1 // indirect
124129
google.golang.org/protobuf v1.36.6 // indirect

go.sum

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
2+
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
13
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
24
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
35
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
@@ -14,6 +16,8 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+
1416
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
1517
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
1618
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
19+
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
20+
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
1721
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
1822
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
1923
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
@@ -117,6 +121,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
117121
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
118122
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
119123
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
124+
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
125+
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
120126
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
121127
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
122128
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -283,12 +289,19 @@ github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
283289
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
284290
github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=
285291
github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
292+
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
293+
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
286294
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
295+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
296+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
287297
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
288298
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
289299
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
290300
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
291301
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
302+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
303+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
304+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
292305
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
293306
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
294307
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
@@ -419,7 +432,6 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
419432
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
420433
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
421434
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
422-
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
423435
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
424436
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
425437
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=

pkg/templates/all/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
// Import all check templates.
55
_ "golang.stackrox.io/kube-linter/pkg/templates/accesstoresources"
66
_ "golang.stackrox.io/kube-linter/pkg/templates/antiaffinity"
7+
_ "golang.stackrox.io/kube-linter/pkg/templates/cel"
78
_ "golang.stackrox.io/kube-linter/pkg/templates/clusteradminrolebinding"
89
_ "golang.stackrox.io/kube-linter/pkg/templates/containercapabilities"
910
_ "golang.stackrox.io/kube-linter/pkg/templates/cpurequirements"

pkg/templates/cel/internal/params/gen-params.go

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package params
2+
3+
// Params defines the configuration parameters for this template.
4+
type Params struct {
5+
// Check contains a CEL expression for validation logic. Two predefined variables are available: 'object' (the current Kubernetes object being processed) and 'objects' (all objects being linted).
6+
// +required
7+
// +noregex
8+
Check string
9+
}

0 commit comments

Comments
 (0)