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:
parent
620ddd80d1
commit
b8c6931aa5
4 changed files with 1061 additions and 0 deletions
103
pkg/cel/autogen/autogen.go
Normal file
103
pkg/cel/autogen/autogen.go
Normal 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
|
||||
}
|
284
pkg/cel/autogen/autogen_test.go
Normal file
284
pkg/cel/autogen/autogen_test.go
Normal 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
212
pkg/cel/autogen/rule.go
Normal 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
|
||||
}
|
462
pkg/cel/autogen/rule_test.go
Normal file
462
pkg/cel/autogen/rule_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue