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

Implement Reporting and Background scan for ImageVerificationPolicy (#12432)

Signed-off-by: Frank Jogeleit <frank.jogeleit@web.de>
This commit is contained in:
Frank Jogeleit 2025-03-18 12:39:00 +01:00 committed by GitHub
parent c0ab93b95b
commit f869638edf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 234 additions and 15 deletions

View file

@ -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,

View file

@ -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
}

View file

@ -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()
})
}

View file

@ -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)
})
}

View file

@ -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{}

View file

@ -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()

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -8,4 +8,5 @@ const (
SourceKyverno = kyverno.ValueKyvernoApp
SourceValidatingAdmissionPolicy = "ValidatingAdmissionPolicy"
SourceValidatingPolicy = "KyvernoValidatingPolicy"
SourceImageVerificationPolicy = "KyvernoImageVerificationPolicy"
)