1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-07 00:17:13 +00:00
kyverno/pkg/cel/engine/provider.go
Mariam Fahmy 76751b96b3
feat: support celexceptions in the CLI apply command (#12182)
* feat: support celexceptions in the CLI

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* feat: add unit tests

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

---------

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
2025-02-19 08:38:44 +00:00

209 lines
6 KiB
Go

package engine
import (
"context"
"fmt"
"sync"
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
"github.com/kyverno/kyverno/pkg/cel/policy"
policiesv1alpha1listers "github.com/kyverno/kyverno/pkg/client/listers/policies.kyverno.io/v1alpha1"
"golang.org/x/exp/maps"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type CompiledPolicy struct {
Actions sets.Set[admissionregistrationv1.ValidationAction]
Policy policiesv1alpha1.ValidatingPolicy
CompiledPolicy policy.CompiledPolicy
}
type Provider interface {
CompiledPolicies(context.Context) ([]CompiledPolicy, error)
}
type ProviderFunc func(context.Context) ([]CompiledPolicy, error)
func (f ProviderFunc) CompiledPolicies(ctx context.Context) ([]CompiledPolicy, error) {
return f(ctx)
}
func NewProvider(compiler policy.Compiler, policies []policiesv1alpha1.ValidatingPolicy, exceptions []*policiesv1alpha1.CELPolicyException) (ProviderFunc, error) {
compiled := make([]CompiledPolicy, 0, len(policies))
for _, vp := range policies {
var matchedExceptions []policiesv1alpha1.CELPolicyException
for _, polex := range exceptions {
for _, ref := range polex.Spec.PolicyRefs {
if ref.Name == vp.GetName() {
matchedExceptions = append(matchedExceptions, *polex)
}
}
}
policy, err := compiler.Compile(&vp, matchedExceptions)
if err != nil {
return nil, fmt.Errorf("failed to compile policy %s (%w)", vp.GetName(), err.ToAggregate())
}
actions := sets.New(vp.Spec.ValidationAction...)
if len(actions) == 0 {
actions.Insert(admissionregistrationv1.Deny)
}
compiled = append(compiled, CompiledPolicy{
Actions: actions,
Policy: vp,
CompiledPolicy: policy,
})
}
provider := func(context.Context) ([]CompiledPolicy, error) {
return compiled, nil
}
return provider, nil
}
func NewKubeProvider(
compiler policy.Compiler,
mgr ctrl.Manager,
polexLister policiesv1alpha1listers.CELPolicyExceptionLister,
) (Provider, error) {
r := newPolicyReconciler(compiler, mgr.GetClient(), polexLister)
err := ctrl.NewControllerManagedBy(mgr).
For(&policiesv1alpha1.ValidatingPolicy{}).
Watches(&policiesv1alpha1.CELPolicyException{}, &handler.Funcs{
CreateFunc: func(
ctx context.Context,
tce event.TypedCreateEvent[client.Object],
trli workqueue.TypedRateLimitingInterface[reconcile.Request],
) {
polex := tce.Object.(*policiesv1alpha1.CELPolicyException)
for _, ref := range polex.Spec.PolicyRefs {
trli.Add(reconcile.Request{
NamespacedName: client.ObjectKey{
Name: ref.Name,
},
})
}
},
UpdateFunc: func(
ctx context.Context,
tue event.TypedUpdateEvent[client.Object],
trli workqueue.TypedRateLimitingInterface[reconcile.Request],
) {
polex := tue.ObjectNew.(*policiesv1alpha1.CELPolicyException)
for _, ref := range polex.Spec.PolicyRefs {
trli.Add(reconcile.Request{
NamespacedName: client.ObjectKey{
Name: ref.Name,
},
})
}
},
DeleteFunc: func(
ctx context.Context,
tde event.TypedDeleteEvent[client.Object],
trli workqueue.TypedRateLimitingInterface[reconcile.Request],
) {
polex := tde.Object.(*policiesv1alpha1.CELPolicyException)
for _, ref := range polex.Spec.PolicyRefs {
trli.Add(reconcile.Request{
NamespacedName: client.ObjectKey{
Name: ref.Name,
},
})
}
},
}).
Complete(r)
if err != nil {
return nil, fmt.Errorf("failed to construct manager: %w", err)
}
return r, nil
}
type policyReconciler struct {
client client.Client
compiler policy.Compiler
lock *sync.RWMutex
policies map[string]CompiledPolicy
polexLister policiesv1alpha1listers.CELPolicyExceptionLister
}
func newPolicyReconciler(
compiler policy.Compiler,
client client.Client,
polexLister policiesv1alpha1listers.CELPolicyExceptionLister,
) *policyReconciler {
return &policyReconciler{
client: client,
compiler: compiler,
lock: &sync.RWMutex{},
policies: map[string]CompiledPolicy{},
polexLister: polexLister,
}
}
func (r *policyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var policy policiesv1alpha1.ValidatingPolicy
err := r.client.Get(ctx, req.NamespacedName, &policy)
if errors.IsNotFound(err) {
r.lock.Lock()
defer r.lock.Unlock()
delete(r.policies, req.NamespacedName.String())
return ctrl.Result{}, nil
}
if err != nil {
return ctrl.Result{}, err
}
// get exceptions that match the policy
exceptions, err := r.ListExceptions(policy.GetName())
if err != nil {
return ctrl.Result{}, err
}
compiled, errs := r.compiler.Compile(&policy, exceptions)
if len(errs) > 0 {
fmt.Println(errs)
// No need to retry it
return ctrl.Result{}, nil
}
r.lock.Lock()
defer r.lock.Unlock()
actions := sets.New(policy.Spec.ValidationAction...)
if len(actions) == 0 {
actions.Insert(admissionregistrationv1.Deny)
}
r.policies[req.NamespacedName.String()] = CompiledPolicy{
Actions: actions,
Policy: policy,
CompiledPolicy: compiled,
}
return ctrl.Result{}, nil
}
func (r *policyReconciler) CompiledPolicies(ctx context.Context) ([]CompiledPolicy, error) {
r.lock.RLock()
defer r.lock.RUnlock()
return maps.Values(r.policies), nil
}
func (r *policyReconciler) ListExceptions(policyName string) ([]policiesv1alpha1.CELPolicyException, error) {
polexList, err := r.polexLister.List(labels.Everything())
if err != nil {
return nil, err
}
var exceptions []policiesv1alpha1.CELPolicyException
for _, polex := range polexList {
for _, ref := range polex.Spec.PolicyRefs {
if ref.Name == policyName {
exceptions = append(exceptions, *polex)
}
}
}
return exceptions, nil
}