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

feat: add MAP's mutation logic for the CLI (#11946)

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
Mariam Fahmy 2025-01-17 17:16:34 +02:00 committed by GitHub
parent a915d28b25
commit 77a7e5193a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 845 additions and 35 deletions

View file

@ -2,8 +2,11 @@ package admissionpolicy
import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/api/admissionregistration/v1alpha1"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apiserver/pkg/admission/plugin/cel"
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
"k8s.io/apiserver/pkg/admission/plugin/policy/validating"
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
"k8s.io/apiserver/pkg/cel/environment"
@ -11,16 +14,14 @@ import (
type Compiler struct {
compositedCompiler cel.CompositedCompiler
// CEL expressions
validateExpressions []admissionregistrationv1beta1.Validation
auditAnnotationExpressions []admissionregistrationv1beta1.AuditAnnotation
matchExpressions []admissionregistrationv1.MatchCondition
variables []admissionregistrationv1beta1.Variable
validations []admissionregistrationv1beta1.Validation
mutations []admissionregistrationv1alpha1.Mutation
auditAnnotations []admissionregistrationv1beta1.AuditAnnotation
matchConditions []admissionregistrationv1.MatchCondition
variables []admissionregistrationv1beta1.Variable
}
func NewCompiler(
validations []admissionregistrationv1beta1.Validation,
auditAnnotations []admissionregistrationv1beta1.AuditAnnotation,
matchConditions []admissionregistrationv1.MatchCondition,
variables []admissionregistrationv1beta1.Variable,
) (*Compiler, error) {
@ -29,14 +30,49 @@ func NewCompiler(
return nil, err
}
return &Compiler{
compositedCompiler: *compositedCompiler,
validateExpressions: validations,
auditAnnotationExpressions: auditAnnotations,
matchExpressions: matchConditions,
variables: variables,
compositedCompiler: *compositedCompiler,
matchConditions: matchConditions,
variables: variables,
}, nil
}
func (c *Compiler) WithValidations(validations []admissionregistrationv1beta1.Validation) {
c.validations = validations
}
func (c *Compiler) WithMutations(mutations []admissionregistrationv1alpha1.Mutation) {
c.mutations = mutations
}
func (c *Compiler) WithAuditAnnotations(auditAnnotations []admissionregistrationv1beta1.AuditAnnotation) {
c.auditAnnotations = auditAnnotations
}
func (c Compiler) CompileMutations(patchOptions cel.OptionalVariableDeclarations) []patch.Patcher {
var patchers []patch.Patcher
for _, m := range c.mutations {
switch m.PatchType {
case v1alpha1.PatchTypeJSONPatch:
if m.JSONPatch != nil {
accessor := &patch.JSONPatchCondition{
Expression: m.JSONPatch.Expression,
}
compileResult := c.compositedCompiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions)
patchers = append(patchers, patch.NewJSONPatcher(compileResult))
}
case v1alpha1.PatchTypeApplyConfiguration:
if m.ApplyConfiguration != nil {
accessor := &patch.ApplyConfigurationCondition{
Expression: m.ApplyConfiguration.Expression,
}
compileResult := c.compositedCompiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions)
patchers = append(patchers, patch.NewApplyConfigurationPatcher(compileResult))
}
}
}
return patchers
}
func (c Compiler) CompileVariables(optionalVars cel.OptionalVariableDeclarations) {
c.compositedCompiler.CompileAndStoreVariables(
c.convertVariables(),
@ -45,7 +81,7 @@ func (c Compiler) CompileVariables(optionalVars cel.OptionalVariableDeclarations
)
}
func (c Compiler) CompileValidateExpressions(optionalVars cel.OptionalVariableDeclarations) cel.ConditionEvaluator {
func (c Compiler) CompileValidations(optionalVars cel.OptionalVariableDeclarations) cel.ConditionEvaluator {
return c.compositedCompiler.CompileCondition(
c.convertValidations(),
optionalVars,
@ -69,17 +105,17 @@ func (c Compiler) CompileAuditAnnotationsExpressions(optionalVars cel.OptionalVa
)
}
func (c Compiler) CompileMatchExpressions(optionalVars cel.OptionalVariableDeclarations) cel.ConditionEvaluator {
func (c Compiler) CompileMatchConditions(optionalVars cel.OptionalVariableDeclarations) cel.ConditionEvaluator {
return c.compositedCompiler.CompileCondition(
c.convertMatchExpressions(),
c.convertMatchConditions(),
optionalVars,
environment.StoredExpressions,
)
}
func (c Compiler) convertValidations() []cel.ExpressionAccessor {
celExpressionAccessor := make([]cel.ExpressionAccessor, len(c.validateExpressions))
for i, validation := range c.validateExpressions {
celExpressionAccessor := make([]cel.ExpressionAccessor, len(c.validations))
for i, validation := range c.validations {
validation := validating.ValidationCondition{
Expression: validation.Expression,
Message: validation.Message,
@ -91,8 +127,8 @@ func (c Compiler) convertValidations() []cel.ExpressionAccessor {
}
func (c Compiler) convertMessageExpressions() []cel.ExpressionAccessor {
celExpressionAccessor := make([]cel.ExpressionAccessor, len(c.validateExpressions))
for i, validation := range c.validateExpressions {
celExpressionAccessor := make([]cel.ExpressionAccessor, len(c.validations))
for i, validation := range c.validations {
if validation.MessageExpression != "" {
condition := validating.MessageExpressionCondition{
MessageExpression: validation.MessageExpression,
@ -104,8 +140,8 @@ func (c Compiler) convertMessageExpressions() []cel.ExpressionAccessor {
}
func (c Compiler) convertAuditAnnotations() []cel.ExpressionAccessor {
celExpressionAccessor := make([]cel.ExpressionAccessor, len(c.auditAnnotationExpressions))
for i, validation := range c.auditAnnotationExpressions {
celExpressionAccessor := make([]cel.ExpressionAccessor, len(c.auditAnnotations))
for i, validation := range c.auditAnnotations {
validation := validating.AuditAnnotationCondition{
Key: validation.Key,
ValueExpression: validation.ValueExpression,
@ -115,9 +151,9 @@ func (c Compiler) convertAuditAnnotations() []cel.ExpressionAccessor {
return celExpressionAccessor
}
func (c Compiler) convertMatchExpressions() []cel.ExpressionAccessor {
celExpressionAccessor := make([]cel.ExpressionAccessor, len(c.matchExpressions))
for i, condition := range c.matchExpressions {
func (c Compiler) convertMatchConditions() []cel.ExpressionAccessor {
celExpressionAccessor := make([]cel.ExpressionAccessor, len(c.matchConditions))
for i, condition := range c.matchConditions {
condition := matchconditions.MatchCondition{
Name: condition.Name,
Expression: condition.Expression,

View file

@ -0,0 +1,135 @@
package admissionpolicy
import (
"context"
"strings"
"time"
celutils "github.com/kyverno/kyverno/pkg/cel/utils"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/managedfields"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/cel"
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
celconfig "k8s.io/apiserver/pkg/apis/cel"
)
func mutateResource(
policy admissionregistrationv1alpha1.MutatingAdmissionPolicy,
resource unstructured.Unstructured,
) (engineapi.EngineResponse, error) {
startTime := time.Now()
engineResponse := engineapi.NewEngineResponse(resource, engineapi.NewMutatingAdmissionPolicy(policy), nil)
policyResp := engineapi.NewPolicyResponse()
gvk := resource.GroupVersionKind()
gvr := schema.GroupVersionResource{
Group: gvk.Group,
Version: gvk.Version,
Resource: strings.ToLower(gvk.Kind) + "s",
}
var namespace *corev1.Namespace
namespaceName := resource.GetNamespace()
// Special case, the namespace object has the namespace of itself.
// unset it if the incoming object is a namespace
if gvk.Kind == "Namespace" && gvk.Version == "v1" && gvk.Group == "" {
namespaceName = ""
}
if namespaceName != "" {
namespace = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespaceName,
},
}
}
a := admission.NewAttributesRecord(resource.DeepCopyObject(), nil, resource.GroupVersionKind(), resource.GetNamespace(), resource.GetName(), gvr, "", admission.Create, nil, false, nil)
versionedAttributes, _ := admission.NewVersionedAttributes(a, a.GetKind(), nil)
o := admission.NewObjectInterfacesFromScheme(runtime.NewScheme())
matchConditions := make([]admissionregistrationv1.MatchCondition, 0, len(policy.Spec.MatchConditions))
for _, m := range policy.Spec.MatchConditions {
matchConditions = append(matchConditions, admissionregistrationv1.MatchCondition(m))
}
variables := make([]admissionregistrationv1beta1.Variable, 0, len(policy.Spec.Variables))
for _, v := range policy.Spec.Variables {
variables = append(variables, admissionregistrationv1beta1.Variable(v))
}
// create compiler
compiler, err := NewCompiler(matchConditions, variables)
if err != nil {
return engineResponse, err
}
compiler.WithMutations(policy.Spec.Mutations)
optionalVars := cel.OptionalVariableDeclarations{
HasParams: false,
HasAuthorizer: false,
HasPatchTypes: true,
StrictCost: true,
}
// compile variables
compiler.CompileVariables(optionalVars)
var failPolicy admissionregistrationv1.FailurePolicyType
if policy.Spec.FailurePolicy == nil {
failPolicy = admissionregistrationv1.Fail
} else {
failPolicy = admissionregistrationv1.FailurePolicyType(*policy.Spec.FailurePolicy)
}
// compile matchers
matcher := matchconditions.NewMatcher(compiler.CompileMatchConditions(optionalVars), &failPolicy, "policy", "mutate", policy.Name)
if matcher != nil {
matchResults := matcher.Match(context.TODO(), versionedAttributes, nil, nil)
// if preconditions are not met, then skip mutations
if !matchResults.Matches {
return engineResponse, nil
}
}
// compile mutations
patchers := compiler.CompileMutations(optionalVars)
// apply mutations
for _, patcher := range patchers {
patchRequest := patch.Request{
MatchedResource: gvr,
VersionedAttributes: versionedAttributes,
ObjectInterfaces: o,
OptionalVariables: cel.OptionalVariableBindings{VersionedParams: nil, Authorizer: nil},
Namespace: namespace,
TypeConverter: managedfields.NewDeducedTypeConverter(),
}
newVersionedObject, err := patcher.Patch(context.TODO(), patchRequest, celconfig.RuntimeCELCostBudget)
if err != nil {
return engineResponse, nil
}
versionedAttributes.Dirty = true
versionedAttributes.VersionedObject = newVersionedObject
}
patchedResource, err := celutils.ConvertObjectToUnstructured(versionedAttributes.VersionedObject)
if err != nil {
return engineResponse, err
}
ruleResp := engineapi.RulePass(policy.GetName(), engineapi.Mutation, "", nil)
policyResp.Add(engineapi.NewExecutionStats(startTime, time.Now()), *ruleResp)
engineResponse = engineResponse.
WithPatchedResource(*patchedResource).
WithPolicyResponse(policyResp)
return engineResponse, nil
}

View file

@ -0,0 +1,577 @@
package admissionpolicy
import (
"encoding/json"
"testing"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"gotest.tools/assert"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
)
func Test_MutateResource(t *testing.T) {
tests := []struct {
name string
rawPolicy []byte
rawResource []byte
expectedRawResource []byte
}{
{
name: "MAP ApplyConfiguration",
rawPolicy: []byte(`{
"apiVersion": "admissionregistration.k8s.io/v1alpha1",
"kind": "MutatingAdmissionPolicy",
"metadata": {
"name": "mutate-policy"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE"
],
"resources": [
"configmaps"
]
}
]
},
"failurePolicy": "Fail",
"mutations": [
{
"patchType": "ApplyConfiguration",
"applyConfiguration": {
"expression": "object.metadata.?labels[\"lfx-mentorship\"].hasValue() ? \n Object{} :\n Object{ metadata: Object.metadata{ labels: {\"lfx-mentorship\": \"kyverno\"}}}\n"
}
}
]
}
}`),
rawResource: []byte(`{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": "game-demo",
"labels": {
"app": "game"
}
},
"data": {
"player_initial_lives": "3"
}
}`),
expectedRawResource: []byte(`{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": "game-demo",
"labels": {
"app": "game",
"lfx-mentorship": "kyverno"
}
},
"data": {
"player_initial_lives": "3"
}
}`),
},
{
name: "MAP JSONPatch",
rawPolicy: []byte(`{
"apiVersion": "admissionregistration.k8s.io/v1alpha1",
"kind": "MutatingAdmissionPolicy",
"metadata": {
"name": "mutate-policy"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
"discovery.k8s.io"
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE"
],
"resources": [
"endpointslices"
]
}
]
},
"failurePolicy": "Fail",
"reinvocationPolicy": "Never",
"mutations": [
{
"patchType": "JSONPatch",
"jsonPatch": {
"expression": "[\n JSONPatch{\n op: \"add\", path: \"/ports\",\n value: object.ports.map(\n p, \n {\n \"name\": p.name,\n \"port\": dyn(p.name.contains(\"secure\") ? 6443 : p.port)\n }\n )\n }\n]\n"
}
}
]
}
}`),
rawResource: []byte(`{
"apiVersion": "discovery.k8s.io/v1",
"kind": "EndpointSlice",
"metadata": {
"name": "example-abc",
"labels": {
"kubernetes.io/service-name": "example"
}
},
"addressType": "IPv4",
"ports": [
{
"name": "http",
"protocol": "TCP",
"port": 80
},
{
"name": "secure",
"protocol": "TCP"
}
],
"endpoints": [
{
"addresses": [
"10.1.2.3"
],
"conditions": {
"ready": true
},
"hostname": "pod-1",
"nodeName": "node-1",
"zone": "us-west2-a"
}
]
}`),
expectedRawResource: []byte(`{
"apiVersion": "discovery.k8s.io/v1",
"kind": "EndpointSlice",
"metadata": {
"name": "example-abc",
"labels": {
"kubernetes.io/service-name": "example"
}
},
"addressType": "IPv4",
"ports": [
{
"name": "http",
"port": 80
},
{
"name": "secure",
"port": 6443
}
],
"endpoints": [
{
"addresses": [
"10.1.2.3"
],
"conditions": {
"ready": true
},
"hostname": "pod-1",
"nodeName": "node-1",
"zone": "us-west2-a"
}
]
}`),
},
{
name: "MAP JSONPatch and ApplyConfiguration",
rawPolicy: []byte(`{
"apiVersion": "admissionregistration.k8s.io/v1alpha1",
"kind": "MutatingAdmissionPolicy",
"metadata": {
"name": "sample-policy"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
"apps"
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE"
],
"resources": [
"deployments"
]
}
]
},
"failurePolicy": "Fail",
"mutations": [
{
"patchType": "ApplyConfiguration",
"applyConfiguration": {
"expression": "Object{\n spec: Object.spec{\n replicas: object.spec.replicas + 100\n }\n}\n"
}
},
{
"patchType": "JSONPatch",
"jsonPatch": {
"expression": "[\n JSONPatch{\n op: \"replace\", \n path: \"/spec/replicas\", \n value: object.spec.replicas + 10\n }\n]\n"
}
}
]
}
}`),
rawResource: []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.14.2",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`),
expectedRawResource: []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 113,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.14.2",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`),
},
{
name: "Two mutations of type ApplyConfigurations",
rawPolicy: []byte(`{
"apiVersion": "admissionregistration.k8s.io/v1alpha1",
"kind": "MutatingAdmissionPolicy",
"metadata": {
"name": "sample-policy"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
"apps"
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE"
],
"resources": [
"deployments"
]
}
]
},
"failurePolicy": "Fail",
"mutations": [
{
"patchType": "ApplyConfiguration",
"applyConfiguration": {
"expression": "Object{\n spec: Object.spec{\n replicas: object.spec.replicas + 100\n }\n}\n"
}
},
{
"patchType": "ApplyConfiguration",
"applyConfiguration": {
"expression": "Object{\n spec: Object.spec{\n replicas: object.spec.replicas + 100\n }\n}\n"
}
}
]
}
}`),
rawResource: []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.14.2",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`),
expectedRawResource: []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 203,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.14.2",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`),
},
{
name: "Two mutations of type JSONPatch",
rawPolicy: []byte(`{
"apiVersion": "admissionregistration.k8s.io/v1alpha1",
"kind": "MutatingAdmissionPolicy",
"metadata": {
"name": "sample-policy"
},
"spec": {
"matchConstraints": {
"resourceRules": [
{
"apiGroups": [
"apps"
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE"
],
"resources": [
"deployments"
]
}
]
},
"failurePolicy": "Fail",
"mutations": [
{
"patchType": "JSONPatch",
"jsonPatch": {
"expression": "[\n JSONPatch{\n op: \"replace\", \n path: \"/spec/replicas\", \n value: object.spec.replicas + 10\n }\n]\n"
}
},
{
"patchType": "JSONPatch",
"jsonPatch": {
"expression": "[\n JSONPatch{\n op: \"replace\", \n path: \"/spec/replicas\", \n value: object.spec.replicas + 10\n }\n]\n"
}
}
]
}
}`),
rawResource: []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.14.2",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`),
expectedRawResource: []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "nginx-deployment",
"labels": {
"app": "nginx"
}
},
"spec": {
"replicas": 23,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.14.2",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
}
}`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expectedResource, err := kubeutils.BytesToUnstructured(tt.expectedRawResource)
assert.NilError(t, err)
var policy admissionregistrationv1alpha1.MutatingAdmissionPolicy
err = json.Unmarshal(tt.rawPolicy, &policy)
assert.NilError(t, err)
resource, err := kubeutils.BytesToUnstructured(tt.rawResource)
assert.NilError(t, err)
response, err := mutateResource(policy, *resource)
assert.NilError(t, err)
assert.DeepEqual(t, expectedResource.Object, response.PatchedResource.Object)
})
}
}

View file

@ -192,10 +192,13 @@ func validateResource(
// compile CEL expressions
matchConditions := ConvertMatchConditionsV1(policy.Spec.MatchConditions)
compiler, err := NewCompiler(policy.Spec.Validations, policy.Spec.AuditAnnotations, matchConditions, policy.Spec.Variables)
compiler, err := NewCompiler(matchConditions, policy.Spec.Variables)
if err != nil {
return engineResponse, err
}
compiler.WithValidations(policy.Spec.Validations)
compiler.WithAuditAnnotations(policy.Spec.AuditAnnotations)
hasParam := policy.Spec.ParamKind != nil
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}
compiler.CompileVariables(optionalVars)
@ -214,9 +217,9 @@ func validateResource(
matchPolicy = *policy.Spec.MatchConstraints.MatchPolicy
}
newMatcher := matchconditions.NewMatcher(compiler.CompileMatchExpressions(optionalVars), &failPolicy, "", string(matchPolicy), "")
newMatcher := matchconditions.NewMatcher(compiler.CompileMatchConditions(optionalVars), &failPolicy, "", string(matchPolicy), "")
validator := validating.NewValidator(
compiler.CompileValidateExpressions(optionalVars),
compiler.CompileValidations(optionalVars),
newMatcher,
compiler.CompileAuditAnnotationsExpressions(optionalVars),
compiler.CompileMessageExpressions(optionalVars),

View file

@ -2,6 +2,7 @@ package api
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -12,8 +13,10 @@ type PolicyType string
const (
// KyvernoPolicy type for kyverno policies
KyvernoPolicyType PolicyType = "KyvernoPolicy"
// ValidatingAdmissionPolicy for validating admission policies
// ValidatingAdmissionPolicy for Kubernetes ValidatingAdmission policies
ValidatingAdmissionPolicyType PolicyType = "ValidatingAdmissionPolicy"
// MutatingAdmissionPolicy for Kubernetes MutatingAdmissionPolicies
MutatingAdmissionPolicyType PolicyType = "MutatingAdmissionPolicy"
)
// GenericPolicy abstracts the policy type (Kyverno policy vs Validating admission policy)
@ -150,3 +153,57 @@ func NewValidatingAdmissionPolicy(pol admissionregistrationv1beta1.ValidatingAdm
policy: pol,
}
}
type MutatingAdmissionPolicy struct {
policy admissionregistrationv1alpha1.MutatingAdmissionPolicy
}
func (p *MutatingAdmissionPolicy) AsKyvernoPolicy() kyvernov1.PolicyInterface {
return nil
}
func (p *MutatingAdmissionPolicy) AsValidatingAdmissionPolicy() *admissionregistrationv1beta1.ValidatingAdmissionPolicy {
return nil
}
func (p *MutatingAdmissionPolicy) GetType() PolicyType {
return MutatingAdmissionPolicyType
}
func (p *MutatingAdmissionPolicy) GetAPIVersion() string {
return "admissionregistration.k8s.io/v1alpha1"
}
func (p *MutatingAdmissionPolicy) GetName() string {
return p.policy.GetName()
}
func (p *MutatingAdmissionPolicy) GetNamespace() string {
return p.policy.GetNamespace()
}
func (p *MutatingAdmissionPolicy) GetKind() string {
return "MutatingAdmissionPolicy"
}
func (p *MutatingAdmissionPolicy) GetResourceVersion() string {
return p.policy.GetResourceVersion()
}
func (p *MutatingAdmissionPolicy) GetAnnotations() map[string]string {
return p.policy.GetAnnotations()
}
func (p *MutatingAdmissionPolicy) IsNamespaced() bool {
return false
}
func (p *MutatingAdmissionPolicy) MetaObject() metav1.Object {
return &p.policy
}
func NewMutatingAdmissionPolicy(pol admissionregistrationv1alpha1.MutatingAdmissionPolicy) GenericPolicy {
return &MutatingAdmissionPolicy{
policy: pol,
}
}

View file

@ -119,15 +119,17 @@ func (h validateCELHandler) Process(
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true}
expressionOptionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false}
// compile CEL expressions
compiler, err := admissionpolicy.NewCompiler(validations, auditAnnotations, admissionpolicy.ConvertMatchConditionsV1(matchConditions), variables)
compiler, err := admissionpolicy.NewCompiler(admissionpolicy.ConvertMatchConditionsV1(matchConditions), variables)
if err != nil {
return resource, handlers.WithError(rule, engineapi.Validation, "Error while creating composited compiler", err)
}
compiler.WithValidations(validations)
compiler.WithAuditAnnotations(auditAnnotations)
compiler.CompileVariables(optionalVars)
filter := compiler.CompileValidateExpressions(optionalVars)
filter := compiler.CompileValidations(optionalVars)
messageExpressionfilter := compiler.CompileMessageExpressions(expressionOptionalVars)
auditAnnotationFilter := compiler.CompileAuditAnnotationsExpressions(optionalVars)
matchConditionFilter := compiler.CompileMatchExpressions(optionalVars)
matchConditionFilter := compiler.CompileMatchConditions(optionalVars)
// newMatcher will be used to check if the incoming resource matches the CEL preconditions
newMatcher := matchconditions.NewMatcher(matchConditionFilter, nil, policyKind, "", policyName)

View file

@ -88,12 +88,12 @@ func checkMatchConditions(logger logr.Logger, policyContext engineapi.PolicyCont
}
optionalVars := cel.OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false}
compiler, err := admissionpolicy.NewCompiler(nil, nil, policy.GetSpec().GetMatchConditions(), nil)
compiler, err := admissionpolicy.NewCompiler(policy.GetSpec().GetMatchConditions(), nil)
if err != nil {
logger.Error(err, "error creating composited compiler")
return false
}
matchConditionFilter := compiler.CompileMatchExpressions(optionalVars)
matchConditionFilter := compiler.CompileMatchConditions(optionalVars)
matcher := matchconditions.NewMatcher(matchConditionFilter, nil, policy.GetKind(), "", policy.GetName())
result := matcher.Match(context.TODO(), versionedAttr, nil, nil)
return result.Matches

View file

@ -541,11 +541,11 @@ func isGlobalContextEntryReady(name string, gctxentries *kyvernov2alpha1.GlobalC
}
func ValidateCustomWebhookMatchConditions(wc []admissionregistrationv1.MatchCondition) error {
c, err := admissionpolicy.NewCompiler(nil, nil, wc, nil)
c, err := admissionpolicy.NewCompiler(wc, nil)
if err != nil {
return err
}
f := c.CompileMatchExpressions(cel.OptionalVariableDeclarations{})
f := c.CompileMatchConditions(cel.OptionalVariableDeclarations{})
if len(f.CompilationErrors()) > 0 {
return fmt.Errorf("match conditions compilation errors: %v", f.CompilationErrors())
}