mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-26 01:24:26 +00:00
* feat: enable mutating webhook for ivpol Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix: unit tests Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix: add objects to payload Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: add chainsaw test Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: add update codegen Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix: propagate policy response to admission reponse Signed-off-by: ShutingZhao <shuting@nirmata.com> * chore: update chainsaw tests Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix: ivpol autogen meta Signed-off-by: ShutingZhao <shuting@nirmata.com> --------- Signed-off-by: ShutingZhao <shuting@nirmata.com>
319 lines
9.4 KiB
Go
319 lines
9.4 KiB
Go
package engine
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
|
"github.com/kyverno/kyverno/pkg/cel/autogen"
|
|
"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/types"
|
|
"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 CompiledValidatingPolicy struct {
|
|
Actions sets.Set[admissionregistrationv1.ValidationAction]
|
|
Policy policiesv1alpha1.ValidatingPolicy
|
|
CompiledPolicy policy.CompiledPolicy
|
|
}
|
|
|
|
type CompiledImageVerificationPolicy struct {
|
|
Policy *policiesv1alpha1.ImageVerificationPolicy
|
|
Actions sets.Set[admissionregistrationv1.ValidationAction]
|
|
}
|
|
|
|
type Provider interface {
|
|
CompiledValidationPolicies(context.Context) ([]CompiledValidatingPolicy, error)
|
|
ImageVerificationPolicies(context.Context) ([]CompiledImageVerificationPolicy, error)
|
|
}
|
|
|
|
type reconcilers struct {
|
|
*policyReconciler
|
|
*ivpolpolicyReconciler
|
|
}
|
|
|
|
type VPolProviderFunc func(context.Context) ([]CompiledValidatingPolicy, error)
|
|
|
|
func (f VPolProviderFunc) CompiledValidationPolicies(ctx context.Context) ([]CompiledValidatingPolicy, error) {
|
|
return f(ctx)
|
|
}
|
|
|
|
type ImageVerifyPolProviderFunc func(context.Context) ([]CompiledImageVerificationPolicy, error)
|
|
|
|
func (f ImageVerifyPolProviderFunc) ImageVerificationPolicies(ctx context.Context) ([]CompiledImageVerificationPolicy, error) {
|
|
return f(ctx)
|
|
}
|
|
|
|
func NewProvider(compiler policy.Compiler, vpolicies []policiesv1alpha1.ValidatingPolicy, exceptions []*policiesv1alpha1.CELPolicyException) (VPolProviderFunc, error) {
|
|
compiled := make([]CompiledValidatingPolicy, 0, len(vpolicies))
|
|
for _, vp := range vpolicies {
|
|
var matchedExceptions []policiesv1alpha1.CELPolicyException
|
|
for _, polex := range exceptions {
|
|
for _, ref := range polex.Spec.PolicyRefs {
|
|
if ref.Name == vp.GetName() && ref.Kind == vp.GetKind() {
|
|
matchedExceptions = append(matchedExceptions, *polex)
|
|
}
|
|
}
|
|
}
|
|
policy, err := compiler.CompileValidating(&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, CompiledValidatingPolicy{
|
|
Actions: actions,
|
|
Policy: vp,
|
|
CompiledPolicy: policy,
|
|
})
|
|
}
|
|
provider := func(context.Context) ([]CompiledValidatingPolicy, error) {
|
|
return compiled, nil
|
|
}
|
|
return provider, nil
|
|
}
|
|
|
|
func NewKubeProvider(
|
|
compiler policy.Compiler,
|
|
mgr ctrl.Manager,
|
|
polexLister policiesv1alpha1listers.CELPolicyExceptionLister,
|
|
) (Provider, error) {
|
|
exceptionHandlerFuncs := &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,
|
|
},
|
|
})
|
|
}
|
|
},
|
|
}
|
|
r := newPolicyReconciler(compiler, mgr.GetClient(), polexLister)
|
|
err := ctrl.NewControllerManagedBy(mgr).
|
|
For(&policiesv1alpha1.ValidatingPolicy{}).
|
|
Watches(&policiesv1alpha1.CELPolicyException{}, exceptionHandlerFuncs).
|
|
Complete(r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to construct manager: %w", err)
|
|
}
|
|
|
|
ivpolr := newivPolicyReconciler(mgr.GetClient(), polexLister)
|
|
err = ctrl.NewControllerManagedBy(mgr).
|
|
For(&policiesv1alpha1.ImageVerificationPolicy{}).
|
|
Watches(&policiesv1alpha1.CELPolicyException{}, exceptionHandlerFuncs).
|
|
Complete(ivpolr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to construct manager: %w", err)
|
|
}
|
|
|
|
return reconcilers{r, ivpolr}, nil
|
|
}
|
|
|
|
type policyReconciler struct {
|
|
client client.Client
|
|
compiler policy.Compiler
|
|
lock *sync.RWMutex
|
|
policies map[string]CompiledValidatingPolicy
|
|
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]CompiledValidatingPolicy{},
|
|
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
|
|
}
|
|
|
|
if policy.GetStatus().Generated {
|
|
r.lock.Lock()
|
|
defer r.lock.Unlock()
|
|
delete(r.policies, req.NamespacedName.String())
|
|
return ctrl.Result{}, nil
|
|
}
|
|
// get exceptions that match the policy
|
|
exceptions, err := listExceptions(r.polexLister, policy.GetName())
|
|
if err != nil {
|
|
return ctrl.Result{}, err
|
|
}
|
|
compiled, errs := r.compiler.CompileValidating(&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()] = CompiledValidatingPolicy{
|
|
Actions: actions,
|
|
Policy: policy,
|
|
CompiledPolicy: compiled,
|
|
}
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
func (r *policyReconciler) CompiledValidationPolicies(ctx context.Context) ([]CompiledValidatingPolicy, error) {
|
|
r.lock.RLock()
|
|
defer r.lock.RUnlock()
|
|
return maps.Values(r.policies), nil
|
|
}
|
|
|
|
func listExceptions(polexLister policiesv1alpha1listers.CELPolicyExceptionLister, policyName string) ([]policiesv1alpha1.CELPolicyException, error) {
|
|
polexList, err := 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
|
|
}
|
|
|
|
type ivpolpolicyReconciler struct {
|
|
client client.Client
|
|
lock *sync.RWMutex
|
|
policies map[string]CompiledImageVerificationPolicy
|
|
polexLister policiesv1alpha1listers.CELPolicyExceptionLister
|
|
}
|
|
|
|
func newivPolicyReconciler(
|
|
client client.Client,
|
|
polexLister policiesv1alpha1listers.CELPolicyExceptionLister,
|
|
) *ivpolpolicyReconciler {
|
|
return &ivpolpolicyReconciler{
|
|
client: client,
|
|
lock: &sync.RWMutex{},
|
|
policies: map[string]CompiledImageVerificationPolicy{},
|
|
polexLister: polexLister,
|
|
}
|
|
}
|
|
|
|
func (r *ivpolpolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
|
var policy policiesv1alpha1.ImageVerificationPolicy
|
|
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
|
|
}
|
|
|
|
// todo: exception support
|
|
// get exceptions that match the policy
|
|
// exceptions, err := listExceptions(r.polexLister, policy.GetName())
|
|
// if err != nil {
|
|
// return ctrl.Result{}, err
|
|
// }
|
|
|
|
autogeneratedIvPols, err := autogen.GetAutogenRulesImageVerify(&policy)
|
|
if err != nil {
|
|
return ctrl.Result{}, err
|
|
}
|
|
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()] = CompiledImageVerificationPolicy{
|
|
Policy: &policy,
|
|
Actions: actions,
|
|
}
|
|
for _, p := range autogeneratedIvPols {
|
|
namespacedName := types.NamespacedName{
|
|
Name: p.Name,
|
|
}
|
|
r.policies[namespacedName.String()] = CompiledImageVerificationPolicy{
|
|
Policy: &policiesv1alpha1.ImageVerificationPolicy{
|
|
Spec: p.Spec,
|
|
},
|
|
Actions: actions,
|
|
}
|
|
}
|
|
return ctrl.Result{}, nil
|
|
}
|
|
|
|
func (r *ivpolpolicyReconciler) ImageVerificationPolicies(ctx context.Context) ([]CompiledImageVerificationPolicy, error) {
|
|
r.lock.RLock()
|
|
defer r.lock.RUnlock()
|
|
return maps.Values(r.policies), nil
|
|
}
|