1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00

feat: add autogen package for ValidatingPolicies (#11996)

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
Mariam Fahmy 2025-01-27 14:36:11 +02:00 committed by GitHub
parent 620ddd80d1
commit b8c6931aa5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1061 additions and 0 deletions

103
pkg/cel/autogen/autogen.go Normal file
View file

@ -0,0 +1,103 @@
package autogen
import (
"strings"
"github.com/kyverno/kyverno/api/kyverno"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"k8s.io/apimachinery/pkg/util/sets"
)
var podControllers = sets.New("daemonsets", "deployments", "jobs", "statefulsets", "replicasets", "cronjobs")
// canAutoGen checks whether the policy can be applied to Pod controllers
// It returns false if:
// - name or selector is defined
// - mixed kinds (Pod + pod controller) is defined
// - Pod is not defined
//
// Otherwise it returns all pod controllers
func canAutoGen(spec *kyvernov2alpha1.ValidatingPolicySpec) (bool, sets.Set[string]) {
match := spec.MatchConstraints
if match.NamespaceSelector != nil {
if len(match.NamespaceSelector.MatchLabels) > 0 || len(match.NamespaceSelector.MatchExpressions) > 0 {
return false, sets.New[string]()
}
}
if match.ObjectSelector != nil {
if len(match.ObjectSelector.MatchLabels) > 0 || len(match.ObjectSelector.MatchExpressions) > 0 {
return false, sets.New[string]()
}
}
rules := match.ResourceRules
for _, rule := range rules {
if len(rule.ResourceNames) > 0 {
return false, sets.New[string]()
}
if len(rule.Resources) > 1 {
return false, sets.New[string]()
}
if rule.Resources[0] != "pods" {
return false, sets.New[string]()
}
}
return true, podControllers
}
func generateRules(spec *kyvernov2alpha1.ValidatingPolicySpec, controllers string) []AutogenRule {
var genRules []AutogenRule
// strip cronjobs from controllers if exist
isRemoved, controllers := stripCronJob(controllers)
// generate rule for pod controllers
if genRule, err := generateRuleForControllers(spec, controllers); err == nil {
genRules = append(genRules, *genRule)
}
// generate rule for cronjobs if exist
if isRemoved {
if genRule, err := generateCronJobRule(spec, "cronjobs"); err == nil {
genRules = append(genRules, *genRule)
}
}
return genRules
}
// stripCronJob removes the cronjobs from controllers
// it returns true, if cronjobs is removed
func stripCronJob(controllers string) (bool, string) {
controllerArr := strings.Split(controllers, ",")
newControllers := make([]string, 0, len(controllerArr))
isRemoved := false
for _, c := range controllerArr {
if c == "cronjobs" {
isRemoved = true
continue
}
newControllers = append(newControllers, c)
}
if len(newControllers) == 0 {
return isRemoved, ""
}
return isRemoved, strings.Join(newControllers, ",")
}
func ComputeRules(policy *kyvernov2alpha1.ValidatingPolicy) []AutogenRule {
applyAutoGen, desiredControllers := canAutoGen(&policy.Spec)
if !applyAutoGen {
return []AutogenRule{}
}
var actualControllers sets.Set[string]
ann := policy.GetAnnotations()
actualControllersString, ok := ann[kyverno.AnnotationAutogenControllers]
if !ok {
actualControllers = desiredControllers
} else {
actualControllers = sets.New(strings.Split(actualControllersString, ",")...)
}
resources := strings.Join(actualControllers.UnsortedList(), ",")
genRules := generateRules(policy.Spec.DeepCopy(), resources)
return genRules
}

View file

@ -0,0 +1,284 @@
package autogen
import (
"encoding/json"
"fmt"
"testing"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"gotest.tools/assert"
"k8s.io/apimachinery/pkg/util/sets"
)
func Test_CanAutoGen(t *testing.T) {
testCases := []struct {
name string
policy []byte
expectedControllers sets.Set[string]
}{
{
name: "policy-with-match-name",
policy: []byte(`{
"apiVersion": "kyverno.io/v2alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "chech-labels"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
],
"resourceNames": [
"test-pod"
]
}
]
},
"variables": [
{
"name": "environment",
"expression": "has(object.metadata.labels) && 'env' in object.metadata.labels && object.metadata.labels['env'] == 'prod'"
}
],
"validations": [
{
"expression": "variables.environment == true",
"message": "Pod labels must be env=prod"
}
]
}
}`),
expectedControllers: sets.New("none"),
},
{
name: "policy-with-match-object-selector",
policy: []byte(`{
"apiVersion": "kyverno.io/v2alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "chech-labels"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
}
],
"objectSelector": {
"matchLabels": {
"app": "nginx"
}
}
},
"variables": [
{
"name": "environment",
"expression": "has(object.metadata.labels) && 'env' in object.metadata.labels && object.metadata.labels['env'] == 'prod'"
}
],
"validations": [
{
"expression": "variables.environment == true",
"message": "Pod labels must be env=prod"
}
]
}
}`),
expectedControllers: sets.New("none"),
},
{
name: "policy-with-match-namespace-selector",
policy: []byte(`{
"apiVersion": "kyverno.io/v2alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "chech-labels"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
}
],
"namespaceSelector": {
"matchLabels": {
"app": "nginx"
}
}
},
"variables": [
{
"name": "environment",
"expression": "has(object.metadata.labels) && 'env' in object.metadata.labels && object.metadata.labels['env'] == 'prod'"
}
],
"validations": [
{
"expression": "variables.environment == true",
"message": "Pod labels must be env=prod"
}
]
}
}`),
expectedControllers: sets.New("none"),
},
{
name: "policy-with-match-mixed-kinds-pod-podcontrollers",
policy: []byte(`{
"apiVersion": "kyverno.io/v2alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "chech-labels"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
},
{
"apiGroups": [
"apps"
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"deployments"
]
}
]
},
"variables": [
{
"name": "environment",
"expression": "has(object.metadata.labels) && 'env' in object.metadata.labels && object.metadata.labels['env'] == 'prod'"
}
],
"validations": [
{
"expression": "variables.environment == true",
"message": "Pod labels must be env=prod"
}
]
}
}`),
expectedControllers: sets.New("none"),
},
{
name: "policy-with-match-kinds-pod-only",
policy: []byte(`{
"apiVersion": "kyverno.io/v2alpha1",
"kind": "ValidatingPolicy",
"metadata": {
"name": "chech-labels"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
}
]
},
"variables": [
{
"name": "environment",
"expression": "has(object.metadata.labels) && 'env' in object.metadata.labels && object.metadata.labels['env'] == 'prod'"
}
],
"validations": [
{
"expression": "variables.environment == true",
"message": "Pod labels must be env=prod"
}
]
}
}`),
expectedControllers: podControllers,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
var policy *kyvernov2alpha1.ValidatingPolicy
err := json.Unmarshal(test.policy, &policy)
assert.NilError(t, err)
applyAutoGen, controllers := canAutoGen(&policy.Spec)
if !applyAutoGen {
controllers = sets.New("none")
}
equalityTest := test.expectedControllers.Equal(controllers)
assert.Assert(t, equalityTest, fmt.Sprintf("expected: %v, got: %v", test.expectedControllers, controllers))
})
}
}

212
pkg/cel/autogen/rule.go Normal file
View file

@ -0,0 +1,212 @@
package autogen
import (
"bytes"
"encoding/json"
"slices"
"strings"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
)
type AutogenRule struct {
MatchConstraints *admissionregistrationv1.MatchResources `json:"matchConstraints,omitempty"`
MatchConditions []admissionregistrationv1.MatchCondition `json:"matchConditions,omitempty"`
Validations []admissionregistrationv1.Validation `json:"validations,omitempty"`
AuditAnnotation []admissionregistrationv1.AuditAnnotation `json:"auditAnnotations,omitempty"`
Variables []admissionregistrationv1.Variable `json:"variables,omitempty"`
}
func generateCronJobRule(spec *kyvernov2alpha1.ValidatingPolicySpec, controllers string) (*AutogenRule, error) {
operations := spec.MatchConstraints.ResourceRules[0].Operations
// create a resource rule for the cronjob resource
matchConstraints := createMatchConstraints(controllers, operations)
// convert match conditions
matchConditions := spec.MatchConditions
if bytes, err := json.Marshal(matchConditions); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, controllers)
if err := json.Unmarshal(bytes, &matchConditions); err != nil {
return nil, err
}
}
// convert validations
validations := spec.Validations
for i := range validations {
if bytes, err := json.Marshal(validations[i]); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, controllers)
if err := json.Unmarshal(bytes, &validations[i]); err != nil {
return nil, err
}
}
}
// convert audit annotations
auditAnnotations := spec.AuditAnnotations
if bytes, err := json.Marshal(auditAnnotations); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, controllers)
if err := json.Unmarshal(bytes, &auditAnnotations); err != nil {
return nil, err
}
}
// convert variables
variables := spec.Variables
if bytes, err := json.Marshal(variables); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, controllers)
if err := json.Unmarshal(bytes, &variables); err != nil {
return nil, err
}
}
return &AutogenRule{
MatchConstraints: matchConstraints,
MatchConditions: matchConditions,
Validations: validations,
AuditAnnotation: auditAnnotations,
Variables: variables,
}, nil
}
func generateRuleForControllers(spec *kyvernov2alpha1.ValidatingPolicySpec, controllers string) (*AutogenRule, error) {
operations := spec.MatchConstraints.ResourceRules[0].Operations
// create a resource rule for pod controllers
matchConstraints := createMatchConstraints(controllers, operations)
// convert match conditions
matchConditions := spec.MatchConditions
if bytes, err := json.Marshal(matchConditions); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, "pods")
if err := json.Unmarshal(bytes, &matchConditions); err != nil {
return nil, err
}
}
// convert validations
validations := spec.Validations
if bytes, err := json.Marshal(validations); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, "pods")
if err := json.Unmarshal(bytes, &validations); err != nil {
return nil, err
}
}
// convert audit annotations
auditAnnotations := spec.AuditAnnotations
if bytes, err := json.Marshal(auditAnnotations); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, "pods")
if err := json.Unmarshal(bytes, &auditAnnotations); err != nil {
return nil, err
}
}
// convert variables
variables := spec.Variables
if bytes, err := json.Marshal(variables); err != nil {
return nil, err
} else {
bytes = updateFields(bytes, "pods")
if err := json.Unmarshal(bytes, &variables); err != nil {
return nil, err
}
}
return &AutogenRule{
MatchConstraints: matchConstraints,
MatchConditions: matchConditions,
Validations: validations,
AuditAnnotation: auditAnnotations,
Variables: variables,
}, nil
}
func createMatchConstraints(controllers string, operations []admissionregistrationv1.OperationType) *admissionregistrationv1.MatchResources {
resources := strings.Split(controllers, ",")
var rules []admissionregistrationv1.NamedRuleWithOperations
for _, resource := range resources {
if resource == "jobs" || resource == "cronjobs" {
rules = append(rules, admissionregistrationv1.NamedRuleWithOperations{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Rule: admissionregistrationv1.Rule{
Resources: []string{resource},
APIGroups: []string{"batch"},
APIVersions: []string{"v1"},
},
Operations: operations,
},
})
} else {
newRule := true
for i := range rules {
if slices.Contains(rules[i].APIGroups, "apps") && slices.Contains(rules[i].APIVersions, "v1") {
rules[i].Resources = append(rules[i].Resources, resource)
newRule = false
break
}
}
if newRule {
rules = append(rules, admissionregistrationv1.NamedRuleWithOperations{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Rule: admissionregistrationv1.Rule{
Resources: []string{resource},
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
},
Operations: operations,
},
})
}
}
}
return &admissionregistrationv1.MatchResources{
ResourceRules: rules,
}
}
var (
podReplacementRules [][2][]byte = [][2][]byte{
{[]byte("object.spec"), []byte("object.spec.template.spec")},
{[]byte("oldObject.spec"), []byte("oldObject.spec.template.spec")},
{[]byte("object.metadata"), []byte("object.spec.template.metadata")},
{[]byte("oldObject.metadata"), []byte("oldObject.spec.template.metadata")},
}
cronJobReplacementRules [][2][]byte = [][2][]byte{
{[]byte("object.spec"), []byte("object.spec.jobTemplate.spec.template.spec")},
{[]byte("oldObject.spec"), []byte("oldObject.spec.jobTemplate.spec.template.spec")},
{[]byte("object.metadata"), []byte("object.spec.jobTemplate.spec.template.metadata")},
{[]byte("oldObject.metadata"), []byte("oldObject.spec.jobTemplate.spec.template.metadata")},
}
)
func updateFields(data []byte, resource string) []byte {
switch resource {
case "pods":
for _, replacement := range podReplacementRules {
data = bytes.ReplaceAll(data, replacement[0], replacement[1])
}
case "cronjobs":
for _, replacement := range cronJobReplacementRules {
data = bytes.ReplaceAll(data, replacement[0], replacement[1])
}
}
return data
}

View file

@ -0,0 +1,462 @@
package autogen
import (
"encoding/json"
"reflect"
"testing"
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
"gotest.tools/assert"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
)
func TestGenerateRuleForControllers(t *testing.T) {
tests := []struct {
name string
controllers string
policySpec []byte
generatedRule AutogenRule
}{
{
name: "autogen rule for deployments",
controllers: "deployments",
policySpec: []byte(`{
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
}
]
},
"validations": [
{
"expression": "object.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)"
}
]
}`),
generatedRule: AutogenRule{
MatchConstraints: &admissionregistrationv1.MatchResources{
ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
admissionregistrationv1.Update,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"deployments"},
},
},
},
},
},
Validations: []admissionregistrationv1.Validation{
{
Expression: "object.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)",
},
},
},
},
{
name: "autogen rule for deployments and daemonsets",
controllers: "deployments,daemonsets",
policySpec: []byte(`{
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
}
]
},
"validations": [
{
"expression": "object.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)"
}
]
}`),
generatedRule: AutogenRule{
MatchConstraints: &admissionregistrationv1.MatchResources{
ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
admissionregistrationv1.Update,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"deployments", "daemonsets"},
},
},
},
},
},
Validations: []admissionregistrationv1.Validation{
{
Expression: "object.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)",
},
},
},
},
{
name: "autogen rule for deployments, daemonsets, statefulsets and replicasets",
controllers: "deployments,daemonsets,statefulsets,replicasets",
policySpec: []byte(`{
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
}
]
},
"matchConditions": [
{
"name": "only for production",
"expression": "has(object.metadata.labels) && has(object.metadata.labels.prod) && object.metadata.labels.prod == 'true'"
}
],
"validations": [
{
"expression": "object.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)"
}
]
}`),
generatedRule: AutogenRule{
MatchConstraints: &admissionregistrationv1.MatchResources{
ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
admissionregistrationv1.Update,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
Resources: []string{"deployments", "daemonsets", "statefulsets", "replicasets"},
},
},
},
},
},
MatchConditions: []admissionregistrationv1.MatchCondition{
{
Name: "only for production",
Expression: "has(object.spec.template.metadata.labels) && has(object.spec.template.metadata.labels.prod) && object.spec.template.metadata.labels.prod == 'true'",
},
},
Validations: []admissionregistrationv1.Validation{
{
Expression: "object.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)",
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var spec *kyvernov2alpha1.ValidatingPolicySpec
err := json.Unmarshal(test.policySpec, &spec)
assert.NilError(t, err)
genRule, err := generateRuleForControllers(spec, test.controllers)
assert.NilError(t, err)
if !reflect.DeepEqual(genRule, &test.generatedRule) {
t.Errorf("generateRuleForControllers() = %v, want %v", genRule, test.generatedRule)
}
})
}
}
func TestGenerateCronJobRule(t *testing.T) {
tests := []struct {
policySpec []byte
generatedRule AutogenRule
}{
{
policySpec: []byte(`{
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
}
]
},
"validations": [
{
"expression": "object.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)"
}
]
}`),
generatedRule: AutogenRule{
MatchConstraints: &admissionregistrationv1.MatchResources{
ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
admissionregistrationv1.Update,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"batch"},
APIVersions: []string{"v1"},
Resources: []string{"cronjobs"},
},
},
},
},
},
Validations: []admissionregistrationv1.Validation{
{
Expression: "object.spec.jobTemplate.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)",
},
},
},
},
{
policySpec: []byte(`{
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
}
]
},
"matchConditions": [
{
"name": "only for production",
"expression": "has(object.metadata.labels) && has(object.metadata.labels.prod) && object.metadata.labels.prod == 'true'"
}
],
"validations": [
{
"expression": "object.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)"
}
]
}`),
generatedRule: AutogenRule{
MatchConstraints: &admissionregistrationv1.MatchResources{
ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
admissionregistrationv1.Update,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"batch"},
APIVersions: []string{"v1"},
Resources: []string{"cronjobs"},
},
},
},
},
},
MatchConditions: []admissionregistrationv1.MatchCondition{
{
Name: "only for production",
Expression: "has(object.spec.jobTemplate.spec.template.metadata.labels) && has(object.spec.jobTemplate.spec.template.metadata.labels.prod) && object.spec.jobTemplate.spec.template.metadata.labels.prod == 'true'",
},
},
Validations: []admissionregistrationv1.Validation{
{
Expression: "object.spec.jobTemplate.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)",
},
},
},
},
{
policySpec: []byte(`{
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"pods"
]
}
]
},
"variables": [
{
"name": "environment",
"expression": "has(object.metadata.labels) && 'env' in object.metadata.labels && object.metadata.labels['env'] == 'prod'"
}
],
"validations": [
{
"expression": "variables.environment == true",
"message": "labels must be env=prod"
}
]
}`),
generatedRule: AutogenRule{
MatchConstraints: &admissionregistrationv1.MatchResources{
ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
admissionregistrationv1.Update,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"batch"},
APIVersions: []string{"v1"},
Resources: []string{"cronjobs"},
},
},
},
},
},
Variables: []admissionregistrationv1.Variable{
{
Name: "environment",
Expression: "has(object.spec.jobTemplate.spec.template.metadata.labels) && 'env' in object.spec.jobTemplate.spec.template.metadata.labels && object.spec.jobTemplate.spec.template.metadata.labels['env'] == 'prod'",
},
},
Validations: []admissionregistrationv1.Validation{
{
Expression: "variables.environment == true",
Message: "labels must be env=prod",
},
},
},
},
}
for _, tt := range tests {
var spec *kyvernov2alpha1.ValidatingPolicySpec
err := json.Unmarshal(tt.policySpec, &spec)
assert.NilError(t, err)
genRule, err := generateCronJobRule(spec, "cronjobs")
assert.NilError(t, err)
if !reflect.DeepEqual(genRule, &tt.generatedRule) {
t.Errorf("generateCronJobRule() = %v, want %v", genRule, tt.generatedRule)
}
}
}
func TestUpdateGenRuleByte(t *testing.T) {
tests := []struct {
pbyte []byte
resource string
want []byte
}{
{
pbyte: []byte("object.spec"),
resource: "pods",
want: []byte("object.spec.template.spec"),
},
{
pbyte: []byte("oldObject.spec"),
resource: "pods",
want: []byte("oldObject.spec.template.spec"),
},
{
pbyte: []byte("object.spec"),
resource: "cronjobs",
want: []byte("object.spec.jobTemplate.spec.template.spec"),
},
{
pbyte: []byte("oldObject.spec"),
resource: "cronjobs",
want: []byte("oldObject.spec.jobTemplate.spec.template.spec"),
},
{
pbyte: []byte("object.metadata"),
resource: "pods",
want: []byte("object.spec.template.metadata"),
},
{
pbyte: []byte("object.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)"),
resource: "cronjobs",
want: []byte("object.spec.jobTemplate.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)"),
},
{
pbyte: []byte("object.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)"),
resource: "pods",
want: []byte("object.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.allowPrivilegeEscalation) && container.securityContext.allowPrivilegeEscalation == false)"),
},
}
for _, tt := range tests {
got := updateFields(tt.pbyte, tt.resource)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("updateGenRuleByte() = %v, want %v", string(got), string(tt.want))
}
}
}