diff --git a/cmd/reports-controller/main.go b/cmd/reports-controller/main.go index e2c47590a7..f4f905287b 100644 --- a/cmd/reports-controller/main.go +++ b/cmd/reports-controller/main.go @@ -83,6 +83,7 @@ func createReportControllers( kyvernoV1.Policies(), kyvernoV1.ClusterPolicies(), policiesV1alpha1.ValidatingPolicies(), + policiesV1alpha1.ImageVerificationPolicies(), vapInformer, ) warmups = append(warmups, func(ctx context.Context) error { @@ -103,6 +104,7 @@ func createReportControllers( kyvernoV1.Policies(), kyvernoV1.ClusterPolicies(), policiesV1alpha1.ValidatingPolicies(), + policiesV1alpha1.ImageVerificationPolicies(), vapInformer, ), aggregationWorkers, @@ -117,6 +119,7 @@ func createReportControllers( kyvernoV1.Policies(), kyvernoV1.ClusterPolicies(), policiesV1alpha1.ValidatingPolicies(), + policiesV1alpha1.ImageVerificationPolicies(), policiesV1alpha1.CELPolicyExceptions(), kyvernoV2.PolicyExceptions(), vapInformer, diff --git a/pkg/cel/engine/ivpolprovider.go b/pkg/cel/engine/ivpolprovider.go new file mode 100644 index 0000000000..c27d06282c --- /dev/null +++ b/pkg/cel/engine/ivpolprovider.go @@ -0,0 +1,43 @@ +package engine + +import ( + "context" + + "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1" + "github.com/kyverno/kyverno/pkg/cel/autogen" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/util/sets" +) + +func NewIVPOLProvider(policies []v1alpha1.ImageVerificationPolicy) (ImageVerifyPolProviderFunc, error) { + compiled := make([]CompiledImageVerificationPolicy, 0, len(policies)) + for _, policy := range policies { + p := policy + actions := sets.New(policy.Spec.ValidationAction...) + if len(actions) == 0 { + actions.Insert(admissionregistrationv1.Deny) + } + compiled = append(compiled, CompiledImageVerificationPolicy{ + Actions: actions, + Policy: &p, + }) + + autogeneratedIvPols, err := autogen.GetAutogenRulesImageVerify(&p) + if err != nil { + return nil, err + } + + for _, ap := range autogeneratedIvPols { + compiled = append(compiled, CompiledImageVerificationPolicy{ + Actions: actions, + Policy: &v1alpha1.ImageVerificationPolicy{ + Spec: ap.Spec, + }, + }) + } + } + provider := func(context.Context) ([]CompiledImageVerificationPolicy, error) { + return compiled, nil + } + return provider, nil +} diff --git a/pkg/cel/policy/filter.go b/pkg/cel/policy/filter.go index a136e899f2..2284dae94d 100644 --- a/pkg/cel/policy/filter.go +++ b/pkg/cel/policy/filter.go @@ -5,8 +5,14 @@ import ( "github.com/kyverno/kyverno/pkg/utils/slices" ) -func RemoveNoneBackgroundPolicies(policies []v1alpha1.ValidatingPolicy) []v1alpha1.ValidatingPolicy { +func RemoveNoneBackgroundValidatingPolicies(policies []v1alpha1.ValidatingPolicy) []v1alpha1.ValidatingPolicy { return slices.Filter(policies, func(vp v1alpha1.ValidatingPolicy) bool { return vp.Spec.BackgroundEnabled() }) } + +func RemoveNoneBackgroundImageVerificationPolicies(policies []v1alpha1.ImageVerificationPolicy) []v1alpha1.ImageVerificationPolicy { + return slices.Filter(policies, func(vp v1alpha1.ImageVerificationPolicy) bool { + return vp.Spec.BackgroundEnabled() + }) +} diff --git a/pkg/cel/policy/filter_test.go b/pkg/cel/policy/filter_test.go index aef78c9145..489f3c2041 100644 --- a/pkg/cel/policy/filter_test.go +++ b/pkg/cel/policy/filter_test.go @@ -55,7 +55,7 @@ func TestRemoveNoneBackgroundPolicies(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := RemoveNoneBackgroundPolicies(tt.policies) + got := RemoveNoneBackgroundValidatingPolicies(tt.policies) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/controllers/report/aggregate/controller.go b/pkg/controllers/report/aggregate/controller.go index 0e1515d789..fc5e339085 100644 --- a/pkg/controllers/report/aggregate/controller.go +++ b/pkg/controllers/report/aggregate/controller.go @@ -53,6 +53,7 @@ type controller struct { polLister kyvernov1listers.PolicyLister cpolLister kyvernov1listers.ClusterPolicyLister vpolLister policiesv1alpha1listers.ValidatingPolicyLister + ivpolLister policiesv1alpha1listers.ImageVerificationPolicyLister vapLister admissionregistrationv1listers.ValidatingAdmissionPolicyLister ephrLister cache.GenericLister cephrLister cache.GenericLister @@ -74,6 +75,7 @@ func NewController( polInformer kyvernov1informers.PolicyInformer, cpolInformer kyvernov1informers.ClusterPolicyInformer, vpolInformer policiesv1alpha1informers.ValidatingPolicyInformer, + ivpolInformer policiesv1alpha1informers.ImageVerificationPolicyInformer, vapInformer admissionregistrationv1informers.ValidatingAdmissionPolicyInformer, ) controllers.Controller { ephrInformer := metadataFactory.ForResource(reportsv1.SchemeGroupVersion.WithResource("ephemeralreports")) @@ -144,6 +146,17 @@ func NewController( logger.Error(err, "failed to register event handlers") } } + if ivpolInformer != nil { + c.ivpolLister = ivpolInformer.Lister() + if _, err := controllerutils.AddEventHandlersT( + ivpolInformer.Informer(), + func(_ metav1.Object) { enqueueAll() }, + func(_, _ metav1.Object) { enqueueAll() }, + func(_ metav1.Object) { enqueueAll() }, + ); err != nil { + logger.Error(err, "failed to register event handlers") + } + } if vapInformer != nil { c.vapLister = vapInformer.Lister() if _, err := controllerutils.AddEventHandlersT( @@ -230,6 +243,20 @@ func (c *controller) createVPolMap() (sets.Set[string], error) { return results, nil } +func (c *controller) createIVPolMap() (sets.Set[string], error) { + results := sets.New[string]() + if c.ivpolLister != nil { + ivpols, err := c.ivpolLister.List(labels.Everything()) + if err != nil { + return nil, err + } + for _, ivpol := range ivpols { + results.Insert(cache.MetaObjectToName(ivpol).String()) + } + } + return results, nil +} + func (c *controller) findOwnedEphemeralReports(ctx context.Context, namespace, name string) ([]reportsv1.ReportInterface, error) { selector, err := reportutils.SelectorResourceUidEquals(types.UID(name)) if err != nil { @@ -454,10 +481,15 @@ func (c *controller) backReconcile(ctx context.Context, logger logr.Logger, _, n if err != nil { return err } + ivpolMap, err := c.createIVPolMap() + if err != nil { + return err + } maps := maps{ - pol: policyMap, - vap: vapMap, - vpol: vpolMap, + pol: policyMap, + vap: vapMap, + vpol: vpolMap, + ivpol: ivpolMap, } reports = append(reports, ephemeralReports...) merged := map[string]policyreportv1alpha2.PolicyReportResult{} diff --git a/pkg/controllers/report/aggregate/controller_test.go b/pkg/controllers/report/aggregate/controller_test.go index a8a6e20a40..377b195c05 100644 --- a/pkg/controllers/report/aggregate/controller_test.go +++ b/pkg/controllers/report/aggregate/controller_test.go @@ -59,7 +59,7 @@ func TestController(t *testing.T) { metaClient.CreateFake(&metav1.PartialObjectMetadata{ObjectMeta: kyvernoPolr.ObjectMeta}, metav1.CreateOptions{}) metaClient.CreateFake(&metav1.PartialObjectMetadata{ObjectMeta: notKyvernoPolr.ObjectMeta}, metav1.CreateOptions{}) - controller := aggregate.NewController(client, nil, metaFactory, polInformer, cpolInformer, nil, nil) + controller := aggregate.NewController(client, nil, metaFactory, polInformer, cpolInformer, nil, nil, nil) ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/pkg/controllers/report/aggregate/utils.go b/pkg/controllers/report/aggregate/utils.go index 9c6d2d836f..f8e78ed0af 100644 --- a/pkg/controllers/report/aggregate/utils.go +++ b/pkg/controllers/report/aggregate/utils.go @@ -16,9 +16,10 @@ import ( ) type maps struct { - pol map[string]policyMapEntry - vap sets.Set[string] - vpol sets.Set[string] + pol map[string]policyMapEntry + vap sets.Set[string] + vpol sets.Set[string] + ivpol sets.Set[string] } func mergeReports(maps maps, accumulator map[string]policyreportv1alpha2.PolicyReportResult, uid types.UID, reports ...reportsv1.ReportInterface) { @@ -37,6 +38,15 @@ func mergeReports(maps maps, accumulator map[string]policyreportv1alpha2.PolicyR accumulator[key] = result } } + case reportutils.SourceImageVerificationPolicy: + if maps.ivpol != nil && maps.ivpol.Has(result.Policy) { + key := result.Source + "/" + result.Policy + "/" + string(uid) + if rule, exists := accumulator[key]; !exists { + accumulator[key] = result + } else if rule.Timestamp.Seconds < result.Timestamp.Seconds { + accumulator[key] = result + } + } case reportutils.SourceValidatingAdmissionPolicy: if maps.vap != nil && maps.vap.Has(result.Policy) { key := result.Source + "/" + result.Policy + "/" + string(uid) diff --git a/pkg/controllers/report/background/controller.go b/pkg/controllers/report/background/controller.go index 5a4ff28455..b7130d6504 100644 --- a/pkg/controllers/report/background/controller.go +++ b/pkg/controllers/report/background/controller.go @@ -65,6 +65,7 @@ type controller struct { polLister kyvernov1listers.PolicyLister cpolLister kyvernov1listers.ClusterPolicyLister vpolLister policiesv1alpha1listers.ValidatingPolicyLister + ivpolLister policiesv1alpha1listers.ImageVerificationPolicyLister polexLister kyvernov2listers.PolicyExceptionLister celpolexListener policiesv1alpha1listers.CELPolicyExceptionLister vapLister admissionregistrationv1listers.ValidatingAdmissionPolicyLister @@ -97,6 +98,7 @@ func NewController( polInformer kyvernov1informers.PolicyInformer, cpolInformer kyvernov1informers.ClusterPolicyInformer, vpolInformer policiesv1alpha1informers.ValidatingPolicyInformer, + ivpolInformer policiesv1alpha1informers.ImageVerificationPolicyInformer, celpolexlInformer policiesv1alpha1informers.CELPolicyExceptionInformer, polexInformer kyvernov2informers.PolicyExceptionInformer, vapInformer admissionregistrationv1informers.ValidatingAdmissionPolicyInformer, @@ -143,6 +145,12 @@ func NewController( logger.Error(err, "failed to register event handlers") } } + if ivpolInformer != nil { + c.ivpolLister = ivpolInformer.Lister() + if _, err := controllerutils.AddEventHandlersT(ivpolInformer.Informer(), c.addIVP, c.updateIVP, c.deleteIVP); err != nil { + logger.Error(err, "failed to register event handlers") + } + } if celpolexlInformer != nil { c.celpolexListener = celpolexlInformer.Lister() if _, err := controllerutils.AddEventHandlersT(celpolexlInformer.Informer(), c.addCELException, c.updateCELException, c.deleteCELPolicy); err != nil { @@ -245,6 +253,20 @@ func (c *controller) deleteVP(obj *policiesv1alpha1.ValidatingPolicy) { c.enqueueResources() } +func (c *controller) addIVP(obj *policiesv1alpha1.ImageVerificationPolicy) { + c.enqueueResources() +} + +func (c *controller) updateIVP(old, obj *policiesv1alpha1.ImageVerificationPolicy) { + if old.GetResourceVersion() != obj.GetResourceVersion() { + c.enqueueResources() + } +} + +func (c *controller) deleteIVP(obj *policiesv1alpha1.ImageVerificationPolicy) { + c.enqueueResources() +} + func (c *controller) addVAP(obj *admissionregistrationv1.ValidatingAdmissionPolicy) { c.enqueueResources() } @@ -418,6 +440,8 @@ func (c *controller) reconcileReport( key = cache.MetaObjectToName(policy.AsValidatingAdmissionPolicy()).String() } else if policy.AsValidatingPolicy() != nil { key = cache.MetaObjectToName(policy.AsValidatingPolicy()).String() + } else if policy.AsImageVerificationPolicy() != nil { + key = cache.MetaObjectToName(policy.AsImageVerificationPolicy()).String() } policyNameToLabel[key] = reportutils.PolicyLabel(policy) } @@ -586,10 +610,20 @@ func (c *controller) reconcile(ctx context.Context, log logr.Logger, key, namesp if err != nil { return err } - for _, vpol := range policy.RemoveNoneBackgroundPolicies(vpols) { + for _, vpol := range policy.RemoveNoneBackgroundValidatingPolicies(vpols) { policies = append(policies, engineapi.NewValidatingPolicy(&vpol)) } } + if c.ivpolLister != nil { + // load validating policies + ivpols, err := utils.FetchImageVerificationPolicies(c.ivpolLister) + if err != nil { + return err + } + for _, vpol := range policy.RemoveNoneBackgroundImageVerificationPolicies(ivpols) { + policies = append(policies, engineapi.NewImageVerificationPolicy(&vpol)) + } + } if c.vapLister != nil { // load validating admission policies vapPolicies, err := utils.FetchValidatingAdmissionPolicies(c.vapLister) diff --git a/pkg/controllers/report/resource/controller.go b/pkg/controllers/report/resource/controller.go index 6db0941f24..34d2c7fbc2 100644 --- a/pkg/controllers/report/resource/controller.go +++ b/pkg/controllers/report/resource/controller.go @@ -79,10 +79,11 @@ type controller struct { client dclient.Interface // listers - polLister kyvernov1listers.PolicyLister - cpolLister kyvernov1listers.ClusterPolicyLister - vpolLister policiesv1alpha1listers.ValidatingPolicyLister - vapLister admissionregistrationv1listers.ValidatingAdmissionPolicyLister + polLister kyvernov1listers.PolicyLister + cpolLister kyvernov1listers.ClusterPolicyLister + vpolLister policiesv1alpha1listers.ValidatingPolicyLister + ivpolLister policiesv1alpha1listers.ImageVerificationPolicyLister + vapLister admissionregistrationv1listers.ValidatingAdmissionPolicyLister // queue queue workqueue.TypedRateLimitingInterface[any] @@ -97,6 +98,7 @@ func NewController( polInformer kyvernov1informers.PolicyInformer, cpolInformer kyvernov1informers.ClusterPolicyInformer, vpolInformer policiesv1alpha1informers.ValidatingPolicyInformer, + ivpolInformer policiesv1alpha1informers.ImageVerificationPolicyInformer, vapInformer admissionregistrationv1informers.ValidatingAdmissionPolicyInformer, ) Controller { c := controller{ @@ -115,6 +117,12 @@ func NewController( logger.Error(err, "failed to register event handlers") } } + if ivpolInformer != nil { + c.ivpolLister = ivpolInformer.Lister() + if _, _, err := controllerutils.AddDefaultEventHandlers(logger, ivpolInformer.Informer(), c.queue); err != nil { + logger.Error(err, "failed to register event handlers") + } + } if vapInformer != nil { c.vapLister = vapInformer.Lister() if _, _, err := controllerutils.AddDefaultEventHandlers(logger, vapInformer.Informer(), c.queue); err != nil { @@ -280,6 +288,20 @@ func (c *controller) updateDynamicWatchers(ctx context.Context) error { } } } + if c.ivpolLister != nil { + ivpols, err := utils.FetchImageVerificationPolicies(c.ivpolLister) + if err != nil { + return err + } + // fetch kinds from image verification admission policies + for _, policy := range ivpols { + kinds := admissionpolicy.GetKinds(policy.Spec.MatchConstraints) + for _, kind := range kinds { + group, version, kind, subresource := kubeutils.ParseKindSelector(kind) + c.addGVKToGVRMapping(group, version, kind, subresource, gvkToGvr) + } + } + } dynamicWatchers := map[schema.GroupVersionResource]*watcher{} for gvk, gvr := range gvkToGvr { logger := logger.WithValues("gvr", gvr, "gvk", gvk) diff --git a/pkg/controllers/report/utils/scanner.go b/pkg/controllers/report/utils/scanner.go index c81f2f04ce..39fad70b80 100644 --- a/pkg/controllers/report/utils/scanner.go +++ b/pkg/controllers/report/utils/scanner.go @@ -82,13 +82,15 @@ func (s *scanner) ScanResource( exceptions []*policiesv1alpha1.CELPolicyException, policies ...engineapi.GenericPolicy, ) map[*engineapi.GenericPolicy]ScanResult { - var kpols, vpols, vaps []engineapi.GenericPolicy + var kpols, vpols, ivpols, vaps []engineapi.GenericPolicy // split policies per nature for _, policy := range policies { if pol := policy.AsKyvernoPolicy(); pol != nil { kpols = append(kpols, policy) } else if pol := policy.AsValidatingPolicy(); pol != nil { vpols = append(vpols, policy) + } else if pol := policy.AsImageVerificationPolicy(); pol != nil { + ivpols = append(vpols, policy) } else if pol := policy.AsValidatingAdmissionPolicy(); pol != nil { vaps = append(vaps, policy) } @@ -195,6 +197,57 @@ func (s *scanner) ScanResource( results[&vpols[i]] = ScanResult{&response, err} } } + + // evaluate image verification policies + for i, policy := range ivpols { + if pol := policy.AsImageVerificationPolicy(); pol != nil { + // create provider + provider, err := celengine.NewIVPOLProvider([]policiesv1alpha1.ImageVerificationPolicy{*pol}) + if err != nil { + logger.Error(err, "failed to create image verification policy provider") + results[&vpols[i]] = ScanResult{nil, err} + continue + } + // create engine + engine := celengine.NewImageVerifyEngine( + provider, + func(name string) *corev1.Namespace { return ns }, + matching.NewMatcher(), + s.client.GetKubeClient().CoreV1().Secrets(""), + nil, + ) + // create context provider + context, err := celpolicy.NewContextProvider(s.client, nil, gctxstore.New()) + if err != nil { + logger.Error(err, "failed to create cel context provider") + results[&vpols[i]] = ScanResult{nil, err} + continue + } + request := celengine.Request( + context, + resource.GroupVersionKind(), + gvr, + subResource, + resource.GetName(), + resource.GetNamespace(), + admissionv1.Create, + authenticationv1.UserInfo{}, + &resource, + nil, + false, + nil, + ) + engineResponse, _, err := engine.HandleMutating(ctx, request) + response := engineapi.EngineResponse{ + Resource: resource, + PolicyResponse: engineapi.PolicyResponse{ + // TODO: policies at index 0 + Rules: []engineapi.RuleResponse{engineResponse.Policies[0].Result}, + }, + }.WithPolicy(vpols[i]) + results[&vpols[i]] = ScanResult{&response, err} + } + } // evaluate validating admission policies for i, policy := range vaps { if pol := policy.AsValidatingAdmissionPolicy(); pol != nil { diff --git a/pkg/controllers/report/utils/utils.go b/pkg/controllers/report/utils/utils.go index e7216ccf29..1773ea7e5e 100644 --- a/pkg/controllers/report/utils/utils.go +++ b/pkg/controllers/report/utils/utils.go @@ -163,6 +163,18 @@ func FetchValidatingPolicies(vpolLister policiesv1alpha1listers.ValidatingPolicy return policies, nil } +func FetchImageVerificationPolicies(ivpolLister policiesv1alpha1listers.ImageVerificationPolicyLister) ([]policiesv1alpha1.ImageVerificationPolicy, error) { + var policies []policiesv1alpha1.ImageVerificationPolicy + if pols, err := ivpolLister.List(labels.Everything()); err != nil { + return nil, err + } else { + for _, pol := range pols { + policies = append(policies, *pol) + } + } + return policies, nil +} + func FetchCELPolicyExceptions(celexLister policiesv1alpha1listers.CELPolicyExceptionLister, namespace string) ([]*policiesv1alpha1.CELPolicyException, error) { exceptions, err := celexLister.CELPolicyExceptions(namespace).List(labels.Everything()) if err != nil { diff --git a/pkg/utils/report/results.go b/pkg/utils/report/results.go index 7f1023005f..949cdc2e9c 100644 --- a/pkg/utils/report/results.go +++ b/pkg/utils/report/results.go @@ -147,6 +147,9 @@ func ToPolicyReportResult(pol engineapi.GenericPolicy, ruleResult engineapi.Rule if pol.AsValidatingPolicy() != nil { result.Source = SourceValidatingPolicy } + if pol.AsImageVerificationPolicy() != nil { + result.Source = SourceImageVerificationPolicy + } return result } diff --git a/pkg/utils/report/source.go b/pkg/utils/report/source.go index 3895160ff0..1e79336db4 100644 --- a/pkg/utils/report/source.go +++ b/pkg/utils/report/source.go @@ -8,4 +8,5 @@ const ( SourceKyverno = kyverno.ValueKyvernoApp SourceValidatingAdmissionPolicy = "ValidatingAdmissionPolicy" SourceValidatingPolicy = "KyvernoValidatingPolicy" + SourceImageVerificationPolicy = "KyvernoImageVerificationPolicy" )