diff --git a/api/kyverno/v1/image_verification_types.go b/api/kyverno/v1/image_verification_types.go index 9bf000aa81..7f30312881 100644 --- a/api/kyverno/v1/image_verification_types.go +++ b/api/kyverno/v1/image_verification_types.go @@ -109,6 +109,11 @@ type ImageVerification struct { // ImageRegistryCredentials provides credentials that will be used for authentication with registry // +kubebuilder:validation:Optional ImageRegistryCredentials *ImageRegistryCredentials `json:"imageRegistryCredentials,omitempty" yaml:"imageRegistryCredentials,omitempty"` + + // UseCache enables caching of image verify responses for this rule + // +kubebuilder:default=true + // +kubebuilder:validation:Optional + UseCache bool `json:"useCache" yaml:"useCache"` } type AttestorSet struct { diff --git a/api/kyverno/v2beta1/image_verification_types.go b/api/kyverno/v2beta1/image_verification_types.go index a735e4a4d5..04efac4a3b 100644 --- a/api/kyverno/v2beta1/image_verification_types.go +++ b/api/kyverno/v2beta1/image_verification_types.go @@ -54,6 +54,11 @@ type ImageVerification struct { // ImageRegistryCredentials provides credentials that will be used for authentication with registry // +kubebuilder:validation:Optional ImageRegistryCredentials *kyvernov1.ImageRegistryCredentials `json:"imageRegistryCredentials,omitempty" yaml:"imageRegistryCredentials,omitempty"` + + // UseCache enables caching of image verify responses for this rule + // +kubebuilder:default=true + // +kubebuilder:validation:Optional + UseCache bool `json:"useCache" yaml:"useCache"` } // Validate implements programmatic validation diff --git a/charts/kyverno/templates/crds/crds.yaml b/charts/kyverno/templates/crds/crds.yaml index c0b7ac3247..3a18bc01b0 100644 --- a/charts/kyverno/templates/crds/crds.yaml +++ b/charts/kyverno/templates/crds/crds.yaml @@ -7748,6 +7748,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -11851,6 +11856,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have @@ -15596,6 +15606,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -19699,6 +19714,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have @@ -23755,6 +23775,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -27859,6 +27884,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have @@ -31605,6 +31635,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -35708,6 +35743,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have diff --git a/cmd/background-controller/main.go b/cmd/background-controller/main.go index 3e0ec0f138..e4f482244f 100644 --- a/cmd/background-controller/main.go +++ b/cmd/background-controller/main.go @@ -157,6 +157,7 @@ func main() { setup.Jp, setup.KyvernoDynamicClient, setup.RegistryClient, + setup.ImageVerifyCacheClient, setup.KubeClient, setup.KyvernoClient, setup.RegistrySecretLister, diff --git a/cmd/cleanup-controller/handlers/cleanup/handlers.go b/cmd/cleanup-controller/handlers/cleanup/handlers.go index 87d77ae18b..c6a895f49d 100644 --- a/cmd/cleanup-controller/handlers/cleanup/handlers.go +++ b/cmd/cleanup-controller/handlers/cleanup/handlers.go @@ -16,6 +16,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/factories" "github.com/kyverno/kyverno/pkg/engine/jmespath" "github.com/kyverno/kyverno/pkg/event" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/metrics" controllerutils "github.com/kyverno/kyverno/pkg/utils/controller" "github.com/kyverno/kyverno/pkg/utils/match" @@ -130,6 +131,7 @@ func (h *handlers) executePolicy( h.jp, h.client, nil, + imageverifycache.DisabledImageVerifyCache(), spec.Context, enginectx, ); err != nil { diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go index c7b83725ca..817cdc144b 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common.go @@ -27,6 +27,7 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/engine/jmespath" "github.com/kyverno/kyverno/pkg/engine/variables/regex" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/logging" datautils "github.com/kyverno/kyverno/pkg/utils/data" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" @@ -790,6 +791,7 @@ func initializeMockController(objects []runtime.Object) (*generate.GenerateContr jmespath.New(cfg), adapters.Client(client), nil, + imageverifycache.DisabledImageVerifyCache(), store.ContextLoaderFactory(nil), nil, "", diff --git a/cmd/cli/kubectl-kyverno/utils/common/kyverno_policies_types.go b/cmd/cli/kubectl-kyverno/utils/common/kyverno_policies_types.go index 9b21d0c654..ebeabb6e97 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/kyverno_policies_types.go +++ b/cmd/cli/kubectl-kyverno/utils/common/kyverno_policies_types.go @@ -15,6 +15,7 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/engine/factories" "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/registryclient" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "k8s.io/apimachinery/pkg/runtime/schema" @@ -119,6 +120,7 @@ OuterLoop: jmespath.New(cfg), adapters.Client(c.Client), factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil), + imageverifycache.DisabledImageVerifyCache(), store.ContextLoaderFactory(nil), nil, "", diff --git a/cmd/cli/kubectl-kyverno/utils/store/contextloader.go b/cmd/cli/kubectl-kyverno/utils/store/contextloader.go index ace392914e..0145f630ba 100644 --- a/cmd/cli/kubectl-kyverno/utils/store/contextloader.go +++ b/cmd/cli/kubectl-kyverno/utils/store/contextloader.go @@ -8,6 +8,7 @@ import ( enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/factories" "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/imageverifycache" ) func ContextLoaderFactory(cmResolver engineapi.ConfigmapResolver) engineapi.ContextLoaderFactory { @@ -48,6 +49,7 @@ func (w wrapper) Load( jp jmespath.Interface, client engineapi.RawClient, rclientFactory engineapi.RegistryClientFactory, + ivCache imageverifycache.Client, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface, ) error { @@ -57,5 +59,5 @@ func (w wrapper) Load( if !GetRegistryAccess() { rclientFactory = nil } - return w.inner.Load(ctx, jp, client, rclientFactory, contextEntries, jsonContext) + return w.inner.Load(ctx, jp, client, rclientFactory, ivCache, contextEntries, jsonContext) } diff --git a/cmd/internal/config.go b/cmd/internal/config.go index 80b6337800..f32a165a3b 100644 --- a/cmd/internal/config.go +++ b/cmd/internal/config.go @@ -14,6 +14,7 @@ type Configuration interface { UsesDeferredLoading() bool UsesCosign() bool UsesRegistryClient() bool + UsesImageVerifyCache() bool UsesLeaderElection() bool UsesKyvernoClient() bool UsesDynamicClient() bool @@ -87,6 +88,12 @@ func WithRegistryClient() ConfigurationOption { } } +func WithImageVerifyCache() ConfigurationOption { + return func(c *configuration) { + c.usesImageVerifyCache = true + } +} + func WithLeaderElection() ConfigurationOption { return func(c *configuration) { c.usesLeaderElection = true @@ -141,6 +148,7 @@ type configuration struct { usesDeferredLoading bool usesCosign bool usesRegistryClient bool + usesImageVerifyCache bool usesLeaderElection bool usesKyvernoClient bool usesDynamicClient bool @@ -186,6 +194,10 @@ func (c *configuration) UsesRegistryClient() bool { return c.usesRegistryClient } +func (c *configuration) UsesImageVerifyCache() bool { + return c.usesImageVerifyCache +} + func (c *configuration) UsesLeaderElection() bool { return c.usesLeaderElection } diff --git a/cmd/internal/engine.go b/cmd/internal/engine.go index cdcabfdc40..a484d90ff8 100644 --- a/cmd/internal/engine.go +++ b/cmd/internal/engine.go @@ -16,6 +16,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/context/resolvers" "github.com/kyverno/kyverno/pkg/engine/factories" "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/registryclient" "k8s.io/client-go/kubernetes" corev1listers "k8s.io/client-go/listers/core/v1" @@ -29,6 +30,7 @@ func NewEngine( jp jmespath.Interface, client dclient.Interface, rclient registryclient.Client, + ivCache imageverifycache.Client, kubeClient kubernetes.Interface, kyvernoClient versioned.Interface, secretLister corev1listers.SecretNamespaceLister, @@ -43,6 +45,7 @@ func NewEngine( jp, adapters.Client(client), factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), secretLister), + ivCache, factories.DefaultContextLoaderFactory(configMapResolver), exceptionsSelector, imageSignatureRepository, diff --git a/cmd/internal/flag.go b/cmd/internal/flag.go index c7bd87ba32..f0b3e4296f 100644 --- a/cmd/internal/flag.go +++ b/cmd/internal/flag.go @@ -44,6 +44,10 @@ var ( registryCredentialHelpers string // leader election leaderElectionRetryPeriod time.Duration + // image verify cache + imageVerifyCacheEnabled bool + imageVerifyCacheTTLDuration int64 + imageVerifyCacheMaxSize int64 ) func initLoggingFlags() { @@ -102,6 +106,12 @@ func initRegistryClientFlags() { flag.StringVar(®istryCredentialHelpers, "registryCredentialHelpers", "", "Credential helpers to enable (default,google,amazon,azure,github). No helpers are added when this flag is empty.") } +func initImageVerifyCacheFlags() { + flag.BoolVar(&imageVerifyCacheEnabled, "imageVerifyCacheEnabled", true, "Whether to use a TTL cache for storing verified images.") + flag.Int64Var(&imageVerifyCacheMaxSize, "imageVerifyCacheMaxSize", 0, "Max size limit for the TTL cache, 0 means no size limit.") + flag.Int64Var(&imageVerifyCacheTTLDuration, "imageVerifyCacheTTLDuration", 0, "Max TTL value for a cache, 0 means no TTL.") +} + func initLeaderElectionFlags() { flag.DurationVar(&leaderElectionRetryPeriod, "leaderElectionRetryPeriod", leaderelection.DefaultRetryPeriod, "Configure leader election retry period.") } @@ -177,6 +187,10 @@ func initFlags(config Configuration, opts ...Option) { if config.UsesRegistryClient() { initRegistryClientFlags() } + // image verify cache + if config.UsesImageVerifyCache() { + initImageVerifyCacheFlags() + } // leader election if config.UsesLeaderElection() { initLeaderElectionFlags() diff --git a/cmd/internal/imageverifycache.go b/cmd/internal/imageverifycache.go new file mode 100644 index 0000000000..648097056e --- /dev/null +++ b/cmd/internal/imageverifycache.go @@ -0,0 +1,23 @@ +package internal + +import ( + "context" + "time" + + "github.com/go-logr/logr" + "github.com/kyverno/kyverno/pkg/imageverifycache" +) + +func setupImageVerifyCache(ctx context.Context, logger logr.Logger) imageverifycache.Client { + logger = logger.WithName("image-verify-cache").WithValues("enabled", imageVerifyCacheEnabled, "maxsize", imageVerifyCacheMaxSize, "ttl", imageVerifyCacheTTLDuration) + logger.Info("setup image verify cache...") + opts := []imageverifycache.Option{ + imageverifycache.WithLogger(logger), + imageverifycache.WithCacheEnableFlag(imageVerifyCacheEnabled), + imageverifycache.WithMaxSize(imageVerifyCacheMaxSize), + imageverifycache.WithTTLDuration(time.Duration(imageVerifyCacheTTLDuration)), + } + imageVerifyCache, err := imageverifycache.New(opts...) + checkError(logger, err, "failed to create image verify cache client") + return imageVerifyCache +} diff --git a/cmd/internal/setup.go b/cmd/internal/setup.go index cccde36ff1..f9f89cb9bf 100644 --- a/cmd/internal/setup.go +++ b/cmd/internal/setup.go @@ -13,6 +13,7 @@ import ( metadataclient "github.com/kyverno/kyverno/pkg/clients/metadata" "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/metrics" "github.com/kyverno/kyverno/pkg/registryclient" corev1listers "k8s.io/client-go/listers/core/v1" @@ -30,20 +31,21 @@ func shutdown(logger logr.Logger, sdowns ...context.CancelFunc) context.CancelFu } type SetupResult struct { - Logger logr.Logger - Configuration config.Configuration - MetricsConfiguration config.MetricsConfiguration - MetricsManager metrics.MetricsConfigManager - Jp jmespath.Interface - KubeClient kubeclient.UpstreamInterface - LeaderElectionClient kubeclient.UpstreamInterface - RegistryClient registryclient.Client - RegistrySecretLister corev1listers.SecretNamespaceLister - KyvernoClient kyvernoclient.UpstreamInterface - DynamicClient dynamicclient.UpstreamInterface - ApiServerClient apiserverclient.UpstreamInterface - MetadataClient metadataclient.UpstreamInterface - KyvernoDynamicClient dclient.Interface + Logger logr.Logger + Configuration config.Configuration + MetricsConfiguration config.MetricsConfiguration + MetricsManager metrics.MetricsConfigManager + Jp jmespath.Interface + KubeClient kubeclient.UpstreamInterface + LeaderElectionClient kubeclient.UpstreamInterface + RegistryClient registryclient.Client + ImageVerifyCacheClient imageverifycache.Client + RegistrySecretLister corev1listers.SecretNamespaceLister + KyvernoClient kyvernoclient.UpstreamInterface + DynamicClient dynamicclient.UpstreamInterface + ApiServerClient apiserverclient.UpstreamInterface + MetadataClient metadataclient.UpstreamInterface + KyvernoDynamicClient dclient.Interface } func Setup(config Configuration, name string, skipResourceFilters bool) (context.Context, SetupResult, context.CancelFunc) { @@ -66,6 +68,10 @@ func Setup(config Configuration, name string, skipResourceFilters bool) (context if config.UsesRegistryClient() { registryClient, registrySecretLister = setupRegistryClient(ctx, logger, client) } + var imageVerifyCache imageverifycache.Client + if config.UsesImageVerifyCache() { + imageVerifyCache = setupImageVerifyCache(ctx, logger) + } var leaderElectionClient kubeclient.UpstreamInterface if config.UsesLeaderElection() { leaderElectionClient = createKubernetesClient(logger, kubeclient.WithMetrics(metricsManager, metrics.KubeClient), kubeclient.WithTracing()) @@ -92,20 +98,21 @@ func Setup(config Configuration, name string, skipResourceFilters bool) (context } return ctx, SetupResult{ - Logger: logger, - Configuration: configuration, - MetricsConfiguration: metricsConfiguration, - MetricsManager: metricsManager, - Jp: jmespath.New(configuration), - KubeClient: client, - LeaderElectionClient: leaderElectionClient, - RegistryClient: registryClient, - RegistrySecretLister: registrySecretLister, - KyvernoClient: kyvernoClient, - DynamicClient: dynamicClient, - ApiServerClient: apiServerClient, - MetadataClient: metadataClient, - KyvernoDynamicClient: dClient, + Logger: logger, + Configuration: configuration, + MetricsConfiguration: metricsConfiguration, + MetricsManager: metricsManager, + Jp: jmespath.New(configuration), + KubeClient: client, + LeaderElectionClient: leaderElectionClient, + RegistryClient: registryClient, + ImageVerifyCacheClient: imageVerifyCache, + RegistrySecretLister: registrySecretLister, + KyvernoClient: kyvernoClient, + DynamicClient: dynamicClient, + ApiServerClient: apiServerClient, + MetadataClient: metadataClient, + KyvernoDynamicClient: dClient, }, shutdown(logger.WithName("shutdown"), sdownMaxProcs, sdownMetrics, sdownTracing, sdownSignals) } diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index f810bd3d23..0fa9fed509 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -216,6 +216,7 @@ func main() { internal.WithDeferredLoading(), internal.WithCosign(), internal.WithRegistryClient(), + internal.WithImageVerifyCache(), internal.WithLeaderElection(), internal.WithKyvernoClient(), internal.WithDynamicClient(), @@ -309,6 +310,7 @@ func main() { setup.Jp, setup.KyvernoDynamicClient, setup.RegistryClient, + setup.ImageVerifyCacheClient, setup.KubeClient, setup.KyvernoClient, setup.RegistrySecretLister, diff --git a/cmd/reports-controller/main.go b/cmd/reports-controller/main.go index aebd042026..30ebfc6952 100644 --- a/cmd/reports-controller/main.go +++ b/cmd/reports-controller/main.go @@ -198,6 +198,7 @@ func main() { internal.WithDeferredLoading(), internal.WithCosign(), internal.WithRegistryClient(), + internal.WithImageVerifyCache(), internal.WithLeaderElection(), internal.WithKyvernoClient(), internal.WithDynamicClient(), @@ -241,6 +242,7 @@ func main() { setup.Jp, setup.KyvernoDynamicClient, setup.RegistryClient, + setup.ImageVerifyCacheClient, setup.KubeClient, setup.KyvernoClient, setup.RegistrySecretLister, diff --git a/config/crds/kyverno.io_clusterpolicies.yaml b/config/crds/kyverno.io_clusterpolicies.yaml index 638a993e7f..1950826e1f 100644 --- a/config/crds/kyverno.io_clusterpolicies.yaml +++ b/config/crds/kyverno.io_clusterpolicies.yaml @@ -3931,6 +3931,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -8034,6 +8039,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have @@ -11779,6 +11789,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -15882,6 +15897,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have diff --git a/config/crds/kyverno.io_policies.yaml b/config/crds/kyverno.io_policies.yaml index fb9642fef2..301883152c 100644 --- a/config/crds/kyverno.io_policies.yaml +++ b/config/crds/kyverno.io_policies.yaml @@ -3932,6 +3932,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -8036,6 +8041,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have @@ -11782,6 +11792,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -15885,6 +15900,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have diff --git a/config/install-latest-testing.yaml b/config/install-latest-testing.yaml index 446cdf3669..41dc039c35 100644 --- a/config/install-latest-testing.yaml +++ b/config/install-latest-testing.yaml @@ -7951,6 +7951,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -12054,6 +12059,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have @@ -15799,6 +15809,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -19902,6 +19917,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have @@ -23958,6 +23978,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -28062,6 +28087,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have @@ -31808,6 +31838,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have a @@ -35911,6 +35946,11 @@ spec: - Cosign - Notary type: string + useCache: + default: true + description: UseCache enables caching of image verify + responses for this rule + type: boolean verifyDigest: default: true description: VerifyDigest validates that images have diff --git a/docs/user/crd/index.html b/docs/user/crd/index.html index 871580b995..5ed81fbc6d 100644 --- a/docs/user/crd/index.html +++ b/docs/user/crd/index.html @@ -2310,6 +2310,17 @@ ImageRegistryCredentials

ImageRegistryCredentials provides credentials that will be used for authentication with registry

+ + +useCache
+ +bool + + + +

UseCache enables caching of image verify responses for this rule

+ +
@@ -6718,6 +6729,17 @@ ImageRegistryCredentials

ImageRegistryCredentials provides credentials that will be used for authentication with registry

+ + +useCache
+ +bool + + + +

UseCache enables caching of image verify responses for this rule

+ +
diff --git a/pkg/client/applyconfigurations/kyverno/v1/imageverification.go b/pkg/client/applyconfigurations/kyverno/v1/imageverification.go index 63d749f120..04d051bd5c 100644 --- a/pkg/client/applyconfigurations/kyverno/v1/imageverification.go +++ b/pkg/client/applyconfigurations/kyverno/v1/imageverification.go @@ -41,6 +41,7 @@ type ImageVerificationApplyConfiguration struct { VerifyDigest *bool `json:"verifyDigest,omitempty"` Required *bool `json:"required,omitempty"` ImageRegistryCredentials *ImageRegistryCredentialsApplyConfiguration `json:"imageRegistryCredentials,omitempty"` + UseCache *bool `json:"useCache,omitempty"` } // ImageVerificationApplyConfiguration constructs an declarative configuration of the ImageVerification type for use with @@ -200,3 +201,11 @@ func (b *ImageVerificationApplyConfiguration) WithImageRegistryCredentials(value b.ImageRegistryCredentials = value return b } + +// WithUseCache sets the UseCache field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UseCache field is set to the value of the last call. +func (b *ImageVerificationApplyConfiguration) WithUseCache(value bool) *ImageVerificationApplyConfiguration { + b.UseCache = &value + return b +} diff --git a/pkg/client/applyconfigurations/kyverno/v2beta1/imageverification.go b/pkg/client/applyconfigurations/kyverno/v2beta1/imageverification.go index 0183ec4a75..4eab917fed 100644 --- a/pkg/client/applyconfigurations/kyverno/v2beta1/imageverification.go +++ b/pkg/client/applyconfigurations/kyverno/v2beta1/imageverification.go @@ -35,6 +35,7 @@ type ImageVerificationApplyConfiguration struct { VerifyDigest *bool `json:"verifyDigest,omitempty"` Required *bool `json:"required,omitempty"` ImageRegistryCredentials *kyvernov1.ImageRegistryCredentialsApplyConfiguration `json:"imageRegistryCredentials,omitempty"` + UseCache *bool `json:"useCache,omitempty"` } // ImageVerificationApplyConfiguration constructs an declarative configuration of the ImageVerification type for use with @@ -126,3 +127,11 @@ func (b *ImageVerificationApplyConfiguration) WithImageRegistryCredentials(value b.ImageRegistryCredentials = value return b } + +// WithUseCache sets the UseCache field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UseCache field is set to the value of the last call. +func (b *ImageVerificationApplyConfiguration) WithUseCache(value bool) *ImageVerificationApplyConfiguration { + b.UseCache = &value + return b +} diff --git a/pkg/engine/api/contextloader.go b/pkg/engine/api/contextloader.go index 732c48fafe..88f699102f 100644 --- a/pkg/engine/api/contextloader.go +++ b/pkg/engine/api/contextloader.go @@ -6,6 +6,7 @@ import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/imageverifycache" ) type RegistryClientFactory interface { @@ -24,6 +25,7 @@ type ContextLoader interface { jp jmespath.Interface, client RawClient, rclientFactory RegistryClientFactory, + ivCache imageverifycache.Client, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface, ) error diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 3a16657c1d..b0d4b552c9 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -15,6 +15,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/internal" "github.com/kyverno/kyverno/pkg/engine/jmespath" engineutils "github.com/kyverno/kyverno/pkg/engine/utils" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/metrics" "github.com/kyverno/kyverno/pkg/tracing" @@ -31,6 +32,7 @@ type engine struct { jp jmespath.Interface client engineapi.Client rclientFactory engineapi.RegistryClientFactory + ivCache imageverifycache.Client contextLoader engineapi.ContextLoaderFactory exceptionSelector engineapi.PolicyExceptionSelector imageSignatureRepository string @@ -47,6 +49,7 @@ func NewEngine( jp jmespath.Interface, client engineapi.Client, rclientFactory engineapi.RegistryClientFactory, + ivCache imageverifycache.Client, contextLoader engineapi.ContextLoaderFactory, exceptionSelector engineapi.PolicyExceptionSelector, imageSignatureRepository string, @@ -72,6 +75,7 @@ func NewEngine( jp: jp, client: client, rclientFactory: rclientFactory, + ivCache: ivCache, contextLoader: contextLoader, exceptionSelector: exceptionSelector, imageSignatureRepository: imageSignatureRepository, @@ -176,6 +180,7 @@ func (e *engine) ContextLoader( e.jp, e.client, e.rclientFactory, + e.ivCache, contextEntries, jsonContext, ) diff --git a/pkg/engine/factories/contextloaderfactory.go b/pkg/engine/factories/contextloaderfactory.go index 12b8b47ae8..245a08a7ba 100644 --- a/pkg/engine/factories/contextloaderfactory.go +++ b/pkg/engine/factories/contextloaderfactory.go @@ -10,6 +10,7 @@ import ( enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/context/loaders" "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/toggle" ) @@ -46,6 +47,7 @@ func (l *contextLoader) Load( jp jmespath.Interface, client engineapi.RawClient, rclientFactory engineapi.RegistryClientFactory, + ivCache imageverifycache.Client, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface, ) error { diff --git a/pkg/engine/handlers/mutation/mutate_image.go b/pkg/engine/handlers/mutation/mutate_image.go index e719c81b57..7440c59c48 100644 --- a/pkg/engine/handlers/mutation/mutate_image.go +++ b/pkg/engine/handlers/mutation/mutate_image.go @@ -14,6 +14,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/mutate/patch" engineutils "github.com/kyverno/kyverno/pkg/engine/utils" "github.com/kyverno/kyverno/pkg/engine/variables" + "github.com/kyverno/kyverno/pkg/imageverifycache" apiutils "github.com/kyverno/kyverno/pkg/utils/api" jsonutils "github.com/kyverno/kyverno/pkg/utils/json" "gomodules.xyz/jsonpatch/v2" @@ -23,6 +24,7 @@ import ( type mutateImageHandler struct { configuration config.Configuration rclientFactory engineapi.RegistryClientFactory + ivCache imageverifycache.Client ivm *engineapi.ImageVerificationMetadata images []apiutils.ImageInfo imageSignatureRepository string @@ -34,6 +36,7 @@ func NewMutateImageHandler( rule kyvernov1.Rule, configuration config.Configuration, rclientFactory engineapi.RegistryClientFactory, + ivCache imageverifycache.Client, ivm *engineapi.ImageVerificationMetadata, imageSignatureRepository string, ) (handlers.Handler, error) { @@ -80,7 +83,7 @@ func (h mutateImageHandler) Process( engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to fetch secrets", err), ) } - iv := internal.NewImageVerifier(logger, rclient, policyContext, *ruleCopy, h.ivm, h.imageSignatureRepository) + iv := internal.NewImageVerifier(logger, rclient, h.ivCache, policyContext, *ruleCopy, h.ivm, h.imageSignatureRepository) patch, ruleResponse := iv.Verify(ctx, imageVerify, h.images, h.configuration) patches = append(patches, patch...) engineResponses = append(engineResponses, ruleResponse...) diff --git a/pkg/engine/image_verify.go b/pkg/engine/image_verify.go index 9378e19e7f..5e2d7e8da0 100644 --- a/pkg/engine/image_verify.go +++ b/pkg/engine/image_verify.go @@ -41,6 +41,7 @@ func (e *engine) verifyAndPatchImages( rule, e.configuration, e.rclientFactory, + e.ivCache, &ivm, e.imageSignatureRepository, ) diff --git a/pkg/engine/image_verify_test.go b/pkg/engine/image_verify_test.go index 0b6ea61437..db7833dafb 100644 --- a/pkg/engine/image_verify_test.go +++ b/pkg/engine/image_verify_test.go @@ -22,6 +22,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/mutate/patch" "github.com/kyverno/kyverno/pkg/engine/policycontext" engineutils "github.com/kyverno/kyverno/pkg/engine/utils" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/registryclient" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "gomodules.xyz/jsonpatch/v2" @@ -185,6 +186,7 @@ func testVerifyAndPatchImages( jp, nil, factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil), + imageverifycache.DisabledImageVerifyCache(), factories.DefaultContextLoaderFactory(cmResolver), nil, "", diff --git a/pkg/engine/internal/imageverifier.go b/pkg/engine/internal/imageverifier.go index 47199da541..7a48730a42 100644 --- a/pkg/engine/internal/imageverifier.go +++ b/pkg/engine/internal/imageverifier.go @@ -17,6 +17,7 @@ import ( enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/images" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/notary" apiutils "github.com/kyverno/kyverno/pkg/utils/api" "github.com/kyverno/kyverno/pkg/utils/jsonpointer" @@ -29,6 +30,7 @@ import ( type ImageVerifier struct { logger logr.Logger rclient engineapi.RegistryClient + ivCache imageverifycache.Client policyContext engineapi.PolicyContext rule kyvernov1.Rule ivm *engineapi.ImageVerificationMetadata @@ -38,6 +40,7 @@ type ImageVerifier struct { func NewImageVerifier( logger logr.Logger, rclient engineapi.RegistryClient, + ivCache imageverifycache.Client, policyContext engineapi.PolicyContext, rule kyvernov1.Rule, ivm *engineapi.ImageVerificationMetadata, @@ -46,6 +49,7 @@ func NewImageVerifier( return &ImageVerifier{ logger: logger, rclient: rclient, + ivCache: ivCache, policyContext: policyContext, rule: rule, ivm: ivm, diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go index c0473f378d..8ca46610a9 100644 --- a/pkg/engine/mutation_test.go +++ b/pkg/engine/mutation_test.go @@ -13,6 +13,7 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/factories" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/registryclient" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" "github.com/stretchr/testify/require" @@ -38,6 +39,7 @@ func testMutate( jp, adapters.Client(client), factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil), + imageverifycache.DisabledImageVerifyCache(), contextLoader, nil, "", diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index c280cf1095..5289646ca1 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -14,6 +14,7 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/factories" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/registryclient" admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" @@ -38,6 +39,7 @@ func testValidate( jp, nil, factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil), + imageverifycache.DisabledImageVerifyCache(), contextLoader, nil, "", diff --git a/pkg/imageverifycache/client.go b/pkg/imageverifycache/client.go new file mode 100644 index 0000000000..15f4edd23e --- /dev/null +++ b/pkg/imageverifycache/client.go @@ -0,0 +1,93 @@ +package imageverifycache + +import ( + "context" + "sync" + "time" + + "github.com/go-logr/logr" + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" +) + +type cache struct { + logger logr.Logger + isCacheEnabled bool + maxSize int64 + ttl time.Duration + lock sync.Mutex +} + +type Option = func(*cache) error + +func New(options ...Option) (Client, error) { + cache := &cache{} + for _, opt := range options { + if err := opt(cache); err != nil { + return nil, err + } + } + + return cache, nil +} + +func DisabledImageVerifyCache() Client { + return &cache{ + logger: logr.Discard(), + isCacheEnabled: false, + maxSize: 0, + ttl: 0, + } +} + +func WithLogger(l logr.Logger) Option { + return func(c *cache) error { + c.logger = l + return nil + } +} + +func WithCacheEnableFlag(b bool) Option { + return func(c *cache) error { + c.isCacheEnabled = b + return nil + } +} + +func WithMaxSize(s int64) Option { + return func(c *cache) error { + c.maxSize = s + return nil + } +} + +func WithTTLDuration(t time.Duration) Option { + return func(c *cache) error { + c.ttl = t + return nil + } +} + +func (c *cache) Set(ctx context.Context, policy kyvernov1.PolicyInterface, ruleName string, imageRef string) (bool, error) { + c.lock.Lock() + defer c.lock.Unlock() + + c.logger.Info("Setting cache", "policy", policy.GetName(), "ruleName", ruleName, "imageRef", imageRef) + if !c.isCacheEnabled { + return false, nil + } + c.logger.Info("Successfully set cache", "policy", policy.GetName(), "ruleName", ruleName, "imageRef", imageRef) + return false, nil +} + +func (c *cache) Get(ctx context.Context, policy kyvernov1.PolicyInterface, ruleName string, imageRef string) (bool, error) { + c.lock.Lock() + defer c.lock.Unlock() + + c.logger.Info("Searching in cache", "policy", policy.GetName(), "ruleName", ruleName, "imageRef", imageRef) + if !c.isCacheEnabled { + return false, nil + } + c.logger.Info("Cache entry not found", "policy", policy.GetName(), "ruleName", ruleName, "imageRef", imageRef) + c.logger.Info("Cache entry found", "policy", policy.GetName(), "ruleName", ruleName, "imageRef", imageRef) + return false, nil +} diff --git a/pkg/imageverifycache/interface.go b/pkg/imageverifycache/interface.go new file mode 100644 index 0000000000..f521c6b0d3 --- /dev/null +++ b/pkg/imageverifycache/interface.go @@ -0,0 +1,18 @@ +package imageverifycache + +import ( + "context" + + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" +) + +type Client interface { + // Set Adds an image to the cache. The image is considered to be verified for the given rule in the policy + // The entry outomatically expires after sometime + // Returns true when the cache entry is added + Set(ctx context.Context, policy kyvernov1.PolicyInterface, ruleName string, imageRef string) (bool, error) + + // Get Searches for the image verified using the rule in the policy in the cache + // Returns true when the cache entry is found + Get(ctx context.Context, policy kyvernov1.PolicyInterface, ruleName string, imagerRef string) (bool, error) +} diff --git a/pkg/webhooks/resource/fake.go b/pkg/webhooks/resource/fake.go index 8a4139cd79..be4ee2900a 100644 --- a/pkg/webhooks/resource/fake.go +++ b/pkg/webhooks/resource/fake.go @@ -13,6 +13,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine/factories" "github.com/kyverno/kyverno/pkg/engine/jmespath" "github.com/kyverno/kyverno/pkg/event" + "github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/metrics" "github.com/kyverno/kyverno/pkg/openapi" "github.com/kyverno/kyverno/pkg/policycache" @@ -60,6 +61,7 @@ func NewFakeHandlers(ctx context.Context, policyCache policycache.Cache) webhook jp, adapters.Client(dclient), factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil), + imageverifycache.DisabledImageVerifyCache(), factories.DefaultContextLoaderFactory(configMapResolver), peLister, "", diff --git a/pkg/webhooks/resource/validation_test.go b/pkg/webhooks/resource/validation_test.go index 4276173cb6..6a5473c2eb 100644 --- a/pkg/webhooks/resource/validation_test.go +++ b/pkg/webhooks/resource/validation_test.go @@ -13,6 +13,7 @@ import ( engineapi "github.com/kyverno/kyverno/pkg/engine/api" "github.com/kyverno/kyverno/pkg/engine/factories" "github.com/kyverno/kyverno/pkg/engine/jmespath" + "github.com/kyverno/kyverno/pkg/imageverifycache" log "github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/registryclient" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" @@ -1060,6 +1061,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { jp, nil, factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil), + imageverifycache.DisabledImageVerifyCache(), factories.DefaultContextLoaderFactory(nil), nil, "", @@ -1162,6 +1164,7 @@ func Test_RuleSelector(t *testing.T) { jp, nil, factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil), + imageverifycache.DisabledImageVerifyCache(), factories.DefaultContextLoaderFactory(nil), nil, "",