diff --git a/api/kyverno/v1/rule_types.go b/api/kyverno/v1/rule_types.go index 6d1eee9b7f..4d12f1a715 100644 --- a/api/kyverno/v1/rule_types.go +++ b/api/kyverno/v1/rule_types.go @@ -160,6 +160,11 @@ func (r *Rule) HasValidateCEL() bool { return r.Validation.CEL != nil && !datautils.DeepEqual(r.Validation.CEL, &CEL{}) } +// HasValidateAssert checks for validate.assert rule +func (r *Rule) HasValidateAssert() bool { + return !datautils.DeepEqual(r.Validation.Assert, AssertionTree{}) +} + // HasValidate checks for validate rule func (r *Rule) HasValidate() bool { return !datautils.DeepEqual(r.Validation, Validation{}) diff --git a/pkg/autogen/autogen.go b/pkg/autogen/autogen.go index ebeced6693..a4a6e85546 100644 --- a/pkg/autogen/autogen.go +++ b/pkg/autogen/autogen.go @@ -19,6 +19,7 @@ const ( var ( PodControllers = sets.New("DaemonSet", "Deployment", "Job", "StatefulSet", "ReplicaSet", "ReplicationController", "CronJob") podControllersKindsSet = PodControllers.Union(sets.New("Pod")) + assertAutogenNodes = []string{"object", "oldObject"} ) func isKindOtherthanPod(kinds []string) bool { @@ -275,3 +276,38 @@ func computeRules(p kyvernov1.PolicyInterface, kind string) []kyvernov1.Rule { out = append(out, genRules...) return out } + +func copyMap(m map[string]any) map[string]any { + newMap := make(map[string]any, len(m)) + for k, v := range m { + newMap[k] = v + } + + return newMap +} + +func createAutogenAssertion(tree kyvernov1.AssertionTree, tplKey string) kyvernov1.AssertionTree { + v, ok := tree.Value.(map[string]any) + if !ok { + return tree + } + + value := copyMap(v) + + for _, n := range assertAutogenNodes { + object, ok := v[n].(map[string]any) + if !ok { + continue + } + + value[n] = map[string]any{ + "spec": map[string]any{ + tplKey: copyMap(object), + }, + } + } + + return kyvernov1.AssertionTree{ + Value: value, + } +} diff --git a/pkg/autogen/autogen_test.go b/pkg/autogen/autogen_test.go index b7c70283bb..31255ee625 100644 --- a/pkg/autogen/autogen_test.go +++ b/pkg/autogen/autogen_test.go @@ -596,3 +596,50 @@ func Test_ValidateWithCELExpressions(t *testing.T) { rules := computeRules(policies[0], "DaemonSet") assert.Equal(t, 2, len(rules)) } + +func Test_ValidateWithAssertion(t *testing.T) { + policy := []byte(` + { + "apiVersion": "kyverno.io/v1", + "kind": "ClusterPolicy", + "metadata": { + "name": "disallow-default-sa" + }, + "spec": { + "validationFailureAction": "Enforce", + "background": false, + "rules": [ + { + "name": "default-sa", + "match": { + "any": [ + { + "resources": { + "kinds": [ + "Pod" + ] + } + } + ] + }, + "validate": { + "assert": { + "object": { + "spec": { + "(serviceAccountName == 'default')": false + } + } + } + } + } + ] + } + } +`) + policies, _, _, err := yamlutils.GetPolicy([]byte(policy)) + assert.NilError(t, err) + assert.Equal(t, 1, len(policies)) + + rules := computeRules(policies[0], "") + assert.Equal(t, 3, len(rules)) +} diff --git a/pkg/autogen/rule.go b/pkg/autogen/rule.go index 698bfe2010..9e909d5e71 100644 --- a/pkg/autogen/rule.go +++ b/pkg/autogen/rule.go @@ -207,6 +207,11 @@ func generateRule(name string, rule *kyvernov1.Rule, tplKey, shift string, kinds rule.Validation.CEL = cel return rule } + if rule.HasValidateAssert() { + rule.Validation.Assert = createAutogenAssertion(*rule.Validation.Assert.DeepCopy(), tplKey) + + return rule + } return nil } diff --git a/test/conformance/chainsaw/autogen/assert-autogen/README.md b/test/conformance/chainsaw/autogen/assert-autogen/README.md new file mode 100644 index 0000000000..b32768eea0 --- /dev/null +++ b/test/conformance/chainsaw/autogen/assert-autogen/README.md @@ -0,0 +1,8 @@ +## Description + +The policy should contain autogen rules for cronjobs and deployments because it has the `pod-policies.kyverno.io/autogen-controllers: Deployment,CronJob` annotation. + +## Expected Behavior + +The policy gets created and contains a autogen rules for cronjobs and deployments in the status. + diff --git a/test/conformance/chainsaw/autogen/assert-autogen/chainsaw-test.yaml b/test/conformance/chainsaw/autogen/assert-autogen/chainsaw-test.yaml new file mode 100755 index 0000000000..ce556cb6c7 --- /dev/null +++ b/test/conformance/chainsaw/autogen/assert-autogen/chainsaw-test.yaml @@ -0,0 +1,13 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: assert-autogen +spec: + steps: + - name: step-01 + try: + - apply: + file: policy.yaml + - assert: + file: policy-assert.yaml diff --git a/test/conformance/chainsaw/autogen/assert-autogen/policy-assert.yaml b/test/conformance/chainsaw/autogen/assert-autogen/policy-assert.yaml new file mode 100644 index 0000000000..c2c973a449 --- /dev/null +++ b/test/conformance/chainsaw/autogen/assert-autogen/policy-assert.yaml @@ -0,0 +1,56 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-default-sa +spec: + validationFailureAction: Audit + rules: + - match: + any: + - resources: + kinds: + - Pod + name: disallow-default-sa + validate: + message: default ServiceAccount should not be used + assert: + object: + spec: + (serviceAccountName == 'default'): false +status: + conditions: + - reason: Succeeded + status: "True" + type: Ready + autogen: + rules: + - match: + any: + - resources: + kinds: + - Deployment + name: autogen-disallow-default-sa + validate: + message: default ServiceAccount should not be used + assert: + object: + spec: + template: + spec: + (serviceAccountName == 'default'): false + - match: + any: + - resources: + kinds: + - CronJob + name: autogen-cronjob-disallow-default-sa + validate: + message: default ServiceAccount should not be used + assert: + object: + spec: + jobTemplate: + spec: + template: + spec: + (serviceAccountName == 'default'): false diff --git a/test/conformance/chainsaw/autogen/assert-autogen/policy.yaml b/test/conformance/chainsaw/autogen/assert-autogen/policy.yaml new file mode 100644 index 0000000000..fbc1f84629 --- /dev/null +++ b/test/conformance/chainsaw/autogen/assert-autogen/policy.yaml @@ -0,0 +1,21 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-default-sa + annotations: + pod-policies.kyverno.io/autogen-controllers: Deployment,CronJob +spec: + validationFailureAction: Audit + rules: + - match: + any: + - resources: + kinds: + - Pod + name: disallow-default-sa + validate: + message: default ServiceAccount should not be used + assert: + object: + spec: + (serviceAccountName == 'default'): false