Skip to content

Commit 192d220

Browse files
authored
ci: testing with cel policies (#519)
* testing with cel policies Signed-off-by: Jaydip Gabani <[email protected]> * --set enableK8sNativeValidation=false for rego engine Signed-off-by: Jaydip Gabani <[email protected]> * fixing uploading artifacts in ci Signed-off-by: Jaydip Gabani <[email protected]> * removing blank line Signed-off-by: Jaydip Gabani <[email protected]> * adding engine rego Signed-off-by: Jaydip Gabani <[email protected]> * adding engine rego Signed-off-by: Jaydip Gabani <[email protected]> * correcting required label cel policy Signed-off-by: Jaydip Gabani <[email protected]> * removing CEL from policies Signed-off-by: Jaydip Gabani <[email protected]> * Using sed to modify gk.yml Signed-off-by: Jaydip Gabani <[email protected]> * adding required label policy and fixing deployment for gk Signed-off-by: Jaydip Gabani <[email protected]> * fixing ci Signed-off-by: Jaydip Gabani <[email protected]> * adding not allowed label value example for required label policy Signed-off-by: Jaydip Gabani <[email protected]> * testing cel with 3.16+ Signed-off-by: Jaydip Gabani <[email protected]> * changing required label cel Signed-off-by: Jaydip Gabani <[email protected]> * only testing rego for 3.15 Signed-off-by: Jaydip Gabani <[email protected]> * fixing ci Signed-off-by: Jaydip Gabani <[email protected]> * fixing ci Signed-off-by: Jaydip Gabani <[email protected]> * fixing examples Signed-off-by: Jaydip Gabani <[email protected]> * fixing CEL code Signed-off-by: Jaydip Gabani <[email protected]> * fixing ci Signed-off-by: Jaydip Gabani <[email protected]> * fixing ci Signed-off-by: Jaydip Gabani <[email protected]> * testing with gk 3.16.1 Signed-off-by: Jaydip Gabani <[email protected]> * testing with gk 3.16.2 Signed-off-by: Jaydip Gabani <[email protected]> --------- Signed-off-by: Jaydip Gabani <[email protected]>
1 parent faa13c6 commit 192d220

File tree

16 files changed

+312
-69
lines changed

16 files changed

+312
-69
lines changed

.github/workflows/workflow.yaml

+15-6
Original file line numberDiff line numberDiff line change
@@ -65,29 +65,35 @@ jobs:
6565
runs-on: ubuntu-latest
6666
strategy:
6767
matrix:
68-
gatekeeper: [ "release-3.13", "release-3.14" ]
69-
name: "Integration test on Gatekeeper ${{ matrix.gatekeeper }}"
68+
gatekeeper: [ "3.15.1", "3.16.2" ]
69+
engine: [ "cel", "rego" ]
70+
name: "Integration test on Gatekeeper ${{ matrix.gatekeeper }} for ${{ matrix.engine }} policies"
7071
steps:
7172
- name: Harden Runner
73+
if: ${{ !(matrix.gatekeeper == '3.15.1' && matrix.engine == 'cel') }} # remove this condition once 3.17 is out
7274
uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1
7375
with:
7476
egress-policy: audit
7577

7678
- name: Check out code into the Go module directory
79+
if: ${{ !(matrix.gatekeeper == '3.15.1' && matrix.engine == 'cel') }}
7780
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
7881

7982
- name: Bootstrap integration test
83+
if: ${{ !(matrix.gatekeeper == '3.15.1' && matrix.engine == 'cel') }}
8084
run: |
8185
mkdir -p $GITHUB_WORKSPACE/bin
8286
echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH
8387
make integration-bootstrap
84-
make deploy GATEKEEPER_VERSION=${{ matrix.gatekeeper }}
88+
make deploy GATEKEEPER_VERSION=${{ matrix.gatekeeper }} POLICY_ENGINE=${{ matrix.engine }}
8589
8690
- name: Run integration test
91+
if: ${{ !(matrix.gatekeeper == '3.15.1' && matrix.engine == 'cel') }}
8792
run: |
8893
make test-integration
8994
9095
- name: Save logs
96+
if: ${{ !(matrix.gatekeeper == '3.15.1' && matrix.engine == 'cel') }}
9197
run: |
9298
kubectl logs -n gatekeeper-system -l control-plane=controller-manager --tail=-1 > logs-controller.json
9399
kubectl logs -n gatekeeper-system -l control-plane=audit-controller --tail=-1 > logs-audit.json
@@ -96,7 +102,7 @@ jobs:
96102
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
97103
if: ${{ always() }}
98104
with:
99-
name: logs-int-test-${{ matrix.gatekeeper }}
105+
name: logs-int-test-${{ matrix.gatekeeper }}-${{ matrix.engine }}
100106
path: |
101107
logs-*.json
102108
require_suites:
@@ -127,7 +133,10 @@ jobs:
127133
make require-sync
128134
gator-verify:
129135
runs-on: ubuntu-latest
130-
name: "Verify assertions in suite.yaml files"
136+
strategy:
137+
matrix:
138+
engine: [ "cel", "rego" ]
139+
name: "Verify assertions in suite.yaml files for ${{ matrix.engine }} policies"
131140
steps:
132141
- name: Harden Runner
133142
uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1
@@ -136,4 +145,4 @@ jobs:
136145

137146
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
138147
- run: |
139-
make verify-gator-dockerized
148+
make verify-gator-dockerized POLICY_ENGINE=${{ matrix.engine }}

Makefile

+22-6
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ KIND_VERSION ?= 0.17.0
33
# note: k8s version pinned since KIND image availability lags k8s releases
44
KUBERNETES_VERSION ?= 1.26.0
55
KUSTOMIZE_VERSION ?= 4.5.5
6-
GATEKEEPER_VERSION ?= release-3.11
6+
GATEKEEPER_VERSION ?= 3.16.0
77
BATS_VERSION ?= 1.8.2
8-
GATOR_VERSION ?= 3.11.0
8+
GATOR_VERSION ?= 3.16.0
99
GOMPLATE_VERSION ?= 3.11.6
10+
POLICY_ENGINE ?= rego
1011

1112
REPO_ROOT := $(shell git rev-parse --show-toplevel)
1213
WEBSITE_SCRIPT_DIR := $(REPO_ROOT)/scripts/website
@@ -31,21 +32,36 @@ integration-bootstrap:
3132
TERM=dumb ${GITHUB_WORKSPACE}/bin/kind create cluster --image kindest/node:v${KUBERNETES_VERSION} --wait 5m --config=test/kind_config.yaml
3233

3334
deploy:
34-
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/${GATEKEEPER_VERSION}/deploy/gatekeeper.yaml
35+
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
36+
ifeq ($(POLICY_ENGINE), rego)
37+
helm install -n gatekeeper-system gatekeeper gatekeeper/gatekeeper --create-namespace --version $(GATEKEEPER_VERSION) --set enableK8sNativeValidation=false
38+
else ifeq ($(POLICY_ENGINE), cel)
39+
ifneq ($(GATEKEEPER_VERSION), 3.15.1)
40+
helm install -n gatekeeper-system gatekeeper gatekeeper/gatekeeper --create-namespace --version $(GATEKEEPER_VERSION) --set enableK8sNativeValidation=true
41+
endif
42+
endif
3543

3644
uninstall:
37-
kubectl delete -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/${GATEKEEPER_VERSION}/deploy/gatekeeper.yaml
45+
helm uninstall -n gatekeeper-system gatekeeper
3846

3947
test-integration:
4048
bats -t test/bats/test.bats
4149

4250
.PHONY: verify-gator
4351
verify-gator:
44-
gator verify ./...
52+
ifeq ($(POLICY_ENGINE), rego)
53+
gator verify ./... --experimental-enable-k8s-native-validation=false
54+
else ifeq ($(POLICY_ENGINE), cel)
55+
gator verify ./... --experimental-enable-k8s-native-validation=true
56+
endif
4557

4658
.PHONY: verify-gator-dockerized
4759
verify-gator-dockerized: __build-gator
48-
$(docker) run -i -v $(shell pwd):/gatekeeper-library gator-container verify ./...
60+
ifeq ($(POLICY_ENGINE), rego)
61+
$(docker) run -i -v $(shell pwd):/gatekeeper-library gator-container verify ./... --experimental-enable-k8s-native-validation=false
62+
else ifeq ($(POLICY_ENGINE), cel)
63+
$(docker) run -i -v $(shell pwd):/gatekeeper-library gator-container verify ./... --experimental-enable-k8s-native-validation=true
64+
endif
4965

5066
.PHONY: build-gator
5167
__build-gator:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
version: 1.1.0
2+
name: k8srequiredlabels
3+
displayName: Required Labels
4+
createdAt: "2024-05-10T23:29:29Z"
5+
description: Requires resources to contain specified labels, with values matching provided regular expressions.
6+
digest: 84897cdfe60dbe9c1726581fe0626d254477a176b143a2cff36b5cf24465e8b8
7+
license: Apache-2.0
8+
homeURL: https://open-policy-agent.github.io/gatekeeper-library/website/requiredlabels
9+
keywords:
10+
- gatekeeper
11+
- open-policy-agent
12+
- policies
13+
readme: |-
14+
# Required Labels
15+
Requires resources to contain specified labels, with values matching provided regular expressions.
16+
install: |-
17+
### Usage
18+
```shell
19+
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/master/artifacthub/library/general/requiredlabels/1.1.0/template.yaml
20+
```
21+
provider:
22+
name: Gatekeeper Library
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
resources:
2+
- template.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: constraints.gatekeeper.sh/v1beta1
2+
kind: K8sRequiredLabels
3+
metadata:
4+
name: all-must-have-owner
5+
spec:
6+
match:
7+
kinds:
8+
- apiGroups: [""]
9+
kinds: ["Namespace"]
10+
parameters:
11+
message: "All namespaces must have an `owner` label that points to your company username"
12+
labels:
13+
- key: owner
14+
allowedRegex: "^[a-zA-Z]+.agilebank.demo$"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: v1
2+
kind: Namespace
3+
metadata:
4+
name: allowed-namespace
5+
labels:
6+
owner: user.agilebank.demo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
apiVersion: v1
2+
kind: Namespace
3+
metadata:
4+
name: disallowed-namespace
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: v1
2+
kind: Namespace
3+
metadata:
4+
name: disallowed-namespace
5+
labels:
6+
owner: user
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
kind: Suite
2+
apiVersion: test.gatekeeper.sh/v1alpha1
3+
metadata:
4+
name: requiredlabels
5+
tests:
6+
- name: must-have-owner
7+
template: template.yaml
8+
constraint: samples/all-must-have-owner/constraint.yaml
9+
cases:
10+
- name: example-allowed
11+
object: samples/all-must-have-owner/example_allowed.yaml
12+
assertions:
13+
- violations: no
14+
- name: example-disallowed
15+
object: samples/all-must-have-owner/example_disallowed.yaml
16+
assertions:
17+
- violations: yes
18+
- name: example-disallowed-label-value
19+
object: samples/all-must-have-owner/example_disallowed_label_value.yaml
20+
assertions:
21+
- violations: yes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
apiVersion: templates.gatekeeper.sh/v1
2+
kind: ConstraintTemplate
3+
metadata:
4+
name: k8srequiredlabels
5+
annotations:
6+
metadata.gatekeeper.sh/title: "Required Labels"
7+
metadata.gatekeeper.sh/version: 1.1.0
8+
description: >-
9+
Requires resources to contain specified labels, with values matching
10+
provided regular expressions.
11+
spec:
12+
crd:
13+
spec:
14+
names:
15+
kind: K8sRequiredLabels
16+
validation:
17+
openAPIV3Schema:
18+
type: object
19+
properties:
20+
message:
21+
type: string
22+
labels:
23+
type: array
24+
description: >-
25+
A list of labels and values the object must specify.
26+
items:
27+
type: object
28+
properties:
29+
key:
30+
type: string
31+
description: >-
32+
The required label.
33+
allowedRegex:
34+
type: string
35+
description: >-
36+
If specified, a regular expression the annotation's value
37+
must match. The value must contain at least one match for
38+
the regular expression.
39+
targets:
40+
- target: admission.k8s.gatekeeper.sh
41+
code:
42+
- engine: K8sNativeValidation
43+
source:
44+
validations:
45+
- expression: '(has(object.metadata) && variables.params.labels.all(entry, has(object.metadata.labels) && entry.key in object.metadata.labels))'
46+
messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")'
47+
- expression: '(has(object.metadata) && variables.params.labels.all(entry, has(object.metadata.labels) && entry.key in object.metadata.labels && string(object.metadata.labels[entry.key]).matches(string(entry.allowedRegex))))'
48+
message: "regex mismatch"
49+
- engine: Rego
50+
source:
51+
rego: |
52+
package k8srequiredlabels
53+
54+
get_message(parameters, _default) := _default {
55+
not parameters.message
56+
}
57+
58+
get_message(parameters, _) := parameters.message
59+
60+
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
61+
provided := {label | input.review.object.metadata.labels[label]}
62+
required := {label | label := input.parameters.labels[_].key}
63+
missing := required - provided
64+
count(missing) > 0
65+
def_msg := sprintf("you must provide labels: %v", [missing])
66+
msg := get_message(input.parameters, def_msg)
67+
}
68+
69+
violation[{"msg": msg}] {
70+
value := input.review.object.metadata.labels[key]
71+
expected := input.parameters.labels[_]
72+
expected.key == key
73+
# do not match if allowedRegex is not defined, or is an empty string
74+
expected.allowedRegex != ""
75+
not regex.match(expected.allowedRegex, value)
76+
def_msg := sprintf("Label <%v: %v> does not satisfy allowed regex: %v", [key, value, expected.allowedRegex])
77+
msg := get_message(input.parameters, def_msg)
78+
}
79+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: v1
2+
kind: Namespace
3+
metadata:
4+
name: disallowed-namespace
5+
labels:
6+
owner: user

library/general/requiredlabels/suite.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ tests:
1515
object: samples/all-must-have-owner/example_disallowed.yaml
1616
assertions:
1717
- violations: yes
18+
- name: example-disallowed-label-value
19+
object: samples/all-must-have-owner/example_disallowed_label_value.yaml
20+
assertions:
21+
- violations: yes

library/general/requiredlabels/template.yaml

+36-25
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ metadata:
44
name: k8srequiredlabels
55
annotations:
66
metadata.gatekeeper.sh/title: "Required Labels"
7-
metadata.gatekeeper.sh/version: 1.0.1
7+
metadata.gatekeeper.sh/version: 1.1.0
88
description: >-
99
Requires resources to contain specified labels, with values matching
1010
provided regular expressions.
@@ -38,31 +38,42 @@ spec:
3838
the regular expression.
3939
targets:
4040
- target: admission.k8s.gatekeeper.sh
41-
rego: |
42-
package k8srequiredlabels
41+
code:
42+
- engine: K8sNativeValidation
43+
source:
44+
validations:
45+
- expression: '(has(object.metadata) && variables.params.labels.all(entry, has(object.metadata.labels) && entry.key in object.metadata.labels))'
46+
messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")'
47+
- expression: '(has(object.metadata) && variables.params.labels.all(entry, has(object.metadata.labels) && entry.key in object.metadata.labels && string(object.metadata.labels[entry.key]).matches(string(entry.allowedRegex))))'
48+
message: "regex mismatch"
49+
- engine: Rego
50+
source:
51+
rego: |
52+
package k8srequiredlabels
4353
44-
get_message(parameters, _default) := _default {
45-
not parameters.message
46-
}
54+
get_message(parameters, _default) := _default {
55+
not parameters.message
56+
}
4757
48-
get_message(parameters, _) := parameters.message
58+
get_message(parameters, _) := parameters.message
4959
50-
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
51-
provided := {label | input.review.object.metadata.labels[label]}
52-
required := {label | label := input.parameters.labels[_].key}
53-
missing := required - provided
54-
count(missing) > 0
55-
def_msg := sprintf("you must provide labels: %v", [missing])
56-
msg := get_message(input.parameters, def_msg)
57-
}
60+
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
61+
provided := {label | input.review.object.metadata.labels[label]}
62+
required := {label | label := input.parameters.labels[_].key}
63+
missing := required - provided
64+
count(missing) > 0
65+
def_msg := sprintf("you must provide labels: %v", [missing])
66+
msg := get_message(input.parameters, def_msg)
67+
}
68+
69+
violation[{"msg": msg}] {
70+
value := input.review.object.metadata.labels[key]
71+
expected := input.parameters.labels[_]
72+
expected.key == key
73+
# do not match if allowedRegex is not defined, or is an empty string
74+
expected.allowedRegex != ""
75+
not regex.match(expected.allowedRegex, value)
76+
def_msg := sprintf("Label <%v: %v> does not satisfy allowed regex: %v", [key, value, expected.allowedRegex])
77+
msg := get_message(input.parameters, def_msg)
78+
}
5879
59-
violation[{"msg": msg}] {
60-
value := input.review.object.metadata.labels[key]
61-
expected := input.parameters.labels[_]
62-
expected.key == key
63-
# do not match if allowedRegex is not defined, or is an empty string
64-
expected.allowedRegex != ""
65-
not regex.match(expected.allowedRegex, value)
66-
def_msg := sprintf("Label <%v: %v> does not satisfy allowed regex: %v", [key, value, expected.allowedRegex])
67-
msg := get_message(input.parameters, def_msg)
68-
}

src/general/requiredlabels/constraint.tmpl

+10-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ metadata:
44
name: k8srequiredlabels
55
annotations:
66
metadata.gatekeeper.sh/title: "Required Labels"
7-
metadata.gatekeeper.sh/version: 1.0.1
7+
metadata.gatekeeper.sh/version: 1.1.0
88
description: >-
99
Requires resources to contain specified labels, with values matching
1010
provided regular expressions.
@@ -38,5 +38,12 @@ spec:
3838
the regular expression.
3939
targets:
4040
- target: admission.k8s.gatekeeper.sh
41-
rego: |
42-
{{ file.Read "src/general/requiredlabels/src.rego" | strings.Indent 8 | strings.TrimSuffix "\n" }}
41+
code:
42+
- engine: K8sNativeValidation
43+
source:
44+
{{ file.Read "src/general/requiredlabels/src.cel" | strings.Indent 10 | strings.TrimSuffix "\n" }}
45+
- engine: Rego
46+
source:
47+
rego: |
48+
{{ file.Read "src/general/requiredlabels/src.rego" | strings.Indent 12 | strings.TrimSuffix "\n" }}
49+

0 commit comments

Comments
 (0)