1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/cel/policy/policy.go
Charles-Edouard Brétéché 2bf7262814
feat: add admission request cel variable (#12054)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
2025-02-03 11:40:05 +00:00

175 lines
5 KiB
Go

package policy
import (
"context"
"fmt"
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
"github.com/kyverno/kyverno/pkg/cel/utils"
"go.uber.org/multierr"
admissionv1 "k8s.io/api/admission/v1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/cel/lazy"
)
type EvaluationResult struct {
Error error
Message string
Result ref.Val
}
type CompiledPolicy interface {
Evaluate(context.Context, admission.Attributes, *admissionv1.AdmissionRequest, runtime.Object, contextlib.ContextInterface) ([]EvaluationResult, error)
}
type compiledValidation struct {
message string
program cel.Program
}
type compiledPolicy struct {
failurePolicy admissionregistrationv1.FailurePolicyType
matchConditions []cel.Program
variables map[string]cel.Program
validations []compiledValidation
auditAnnotations map[string]cel.Program
}
func (p *compiledPolicy) Evaluate(
ctx context.Context,
attr admission.Attributes,
request *admissionv1.AdmissionRequest,
namespace runtime.Object,
context contextlib.ContextInterface,
) ([]EvaluationResult, error) {
match, err := p.match(ctx, attr, request, namespace)
if err != nil {
return nil, err
}
if !match {
return nil, nil
}
namespaceVal, err := objectToResolveVal(namespace)
if err != nil {
return nil, fmt.Errorf("failed to prepare namespace variable for evaluation: %w", err)
}
objectVal, err := objectToResolveVal(attr.GetObject())
if err != nil {
return nil, fmt.Errorf("failed to prepare object variable for evaluation: %w", err)
}
oldObjectVal, err := objectToResolveVal(attr.GetOldObject())
if err != nil {
return nil, fmt.Errorf("failed to prepare oldObject variable for evaluation: %w", err)
}
requestVal, err := convertObjectToUnstructured(request)
if err != nil {
return nil, fmt.Errorf("failed to prepare request variable for evaluation: %w", err)
}
vars := lazy.NewMapValue(VariablesType)
data := map[string]any{
ContextKey: contextlib.Context{ContextInterface: context},
NamespaceObjectKey: namespaceVal,
ObjectKey: objectVal,
OldObjectKey: oldObjectVal,
RequestKey: requestVal.Object,
VariablesKey: vars,
}
for name, variable := range p.variables {
vars.Append(name, func(*lazy.MapValue) ref.Val {
out, _, err := variable.Eval(data)
if out != nil {
return out
}
if err != nil {
return types.WrapErr(err)
}
return nil
})
}
results := make([]EvaluationResult, 0, len(p.validations))
for _, validation := range p.validations {
out, _, err := validation.program.Eval(data)
results = append(results, EvaluationResult{
Result: out,
Message: validation.message,
Error: err,
})
}
return results, nil
}
func (p *compiledPolicy) match(ctx context.Context, attr admission.Attributes, request *admissionv1.AdmissionRequest, namespace runtime.Object) (bool, error) {
namespaceVal, err := objectToResolveVal(namespace)
if err != nil {
return false, fmt.Errorf("failed to prepare namespace variable for evaluation: %w", err)
}
objectVal, err := objectToResolveVal(attr.GetObject())
if err != nil {
return false, fmt.Errorf("failed to prepare object variable for evaluation: %w", err)
}
oldObjectVal, err := objectToResolveVal(attr.GetOldObject())
if err != nil {
return false, fmt.Errorf("failed to prepare oldObject variable for evaluation: %w", err)
}
requestVal, err := convertObjectToUnstructured(request)
if err != nil {
return false, fmt.Errorf("failed to prepare request variable for evaluation: %w", err)
}
data := map[string]any{
NamespaceObjectKey: namespaceVal,
ObjectKey: objectVal,
OldObjectKey: oldObjectVal,
RequestKey: requestVal.Object,
}
var errs []error
for _, matchCondition := range p.matchConditions {
// evaluate the condition
out, _, err := matchCondition.ContextEval(ctx, data)
// check error
if err != nil {
errs = append(errs, err)
continue
}
// try to convert to a bool
result, err := utils.ConvertToNative[bool](out)
// check error
if err != nil {
errs = append(errs, err)
continue
}
// if condition is false, skip
if !result {
return false, nil
}
}
return true, multierr.Combine(errs...)
}
func convertObjectToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
if obj == nil || reflect.ValueOf(obj).IsNil() {
return &unstructured.Unstructured{Object: nil}, nil
}
ret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: ret}, nil
}
func objectToResolveVal(r runtime.Object) (interface{}, error) {
if r == nil || reflect.ValueOf(r).IsNil() {
return nil, nil
}
v, err := convertObjectToUnstructured(r)
if err != nil {
return nil, err
}
return v.Object, nil
}