From c429f845dd8ea0676edc8011114ba127d8d364be Mon Sep 17 00:00:00 2001 From: Njegos Railic Date: Mon, 2 Jan 2023 18:14:40 +0100 Subject: [PATCH] Adding support for overriding the default registry (#4715) Signed-off-by: Njegos Railic Signed-off-by: Njegos Railic --- charts/kyverno/README.md | 2 + charts/kyverno/templates/configmap.yaml | 6 + charts/kyverno/values.yaml | 6 + .../handlers/cleanup/handlers.go | 9 +- cmd/cleanup-controller/main.go | 1 + cmd/cleanup-controller/server.go | 5 +- .../kubectl-kyverno/utils/common/common.go | 8 +- cmd/kyverno/main.go | 3 + config/install.yaml | 2 + pkg/background/common/context.go | 2 +- pkg/config/config.go | 59 +++++++- .../report/background/controller.go | 9 +- pkg/controllers/report/utils/scanner.go | 13 +- pkg/engine/context/context.go | 17 ++- pkg/engine/imageVerify.go | 17 ++- pkg/engine/imageVerifyValidate.go | 5 +- pkg/engine/imageVerify_test.go | 45 +++--- pkg/engine/mutation_test.go | 12 +- pkg/engine/policyContext.go | 2 +- pkg/engine/validation.go | 9 +- pkg/engine/validation_test.go | 64 ++++---- pkg/policy/apply.go | 6 +- pkg/policy/policy_controller.go | 2 +- pkg/policy/validate.go | 2 +- pkg/policy/validate_test.go | 2 +- pkg/testrunner/scenario.go | 4 +- pkg/utils/api/image.go | 19 +-- pkg/utils/api/image_test.go | 5 +- pkg/utils/image/infos.go | 63 ++++++-- pkg/utils/image/infos_test.go | 137 ++++++++++++++++-- pkg/webhooks/resource/handlers.go | 4 +- .../resource/imageverification/handler.go | 6 +- .../resource/validation/validation.go | 8 +- pkg/webhooks/resource/validation_test.go | 26 ++-- 34 files changed, 421 insertions(+), 159 deletions(-) diff --git a/charts/kyverno/README.md b/charts/kyverno/README.md index f79d11ef8c..9c5955981b 100644 --- a/charts/kyverno/README.md +++ b/charts/kyverno/README.md @@ -177,6 +177,8 @@ The command removes all the Kubernetes components associated with the chart and | generatecontrollerExtraResources | list | `[]` | Additional resources to be added to controller RBAC permissions. | | excludeKyvernoNamespace | bool | `true` | Exclude Kyverno namespace Determines if default Kyverno namespace exclusion is enabled for webhooks and resourceFilters | | resourceFiltersExcludeNamespaces | list | `[]` | resourceFilter namespace exclude Namespaces to exclude from the default resourceFilters | +| config.defaultRegistry | string | `"docker.io"` | The registry hostname used for the image mutation. | +| config.enableDefaultRegistryMutation | bool | `true` | Enable registry mutation for container images. Enabled by default. | | config.resourceFilters | list | See [values.yaml](values.yaml) | Resource types to be skipped by the Kyverno policy engine. Make sure to surround each entry in quotes so that it doesn't get parsed as a nested YAML list. These are joined together without spaces, run through `tpl`, and the result is set in the config map. | | config.existingConfig | string | `""` | Name of an existing config map (ignores default/provided resourceFilters) | | config.annotations | object | `{}` | Additional annotations to add to the configmap | diff --git a/charts/kyverno/templates/configmap.yaml b/charts/kyverno/templates/configmap.yaml index 8125dd6462..10c7fd2e08 100644 --- a/charts/kyverno/templates/configmap.yaml +++ b/charts/kyverno/templates/configmap.yaml @@ -11,6 +11,12 @@ metadata: name: {{ template "kyverno.configMapName" . }} namespace: {{ template "kyverno.namespace" . }} data: + {{- if .Values.config.defaultRegistry }} + defaultRegistry: {{ .Values.config.defaultRegistry | quote }} + {{- end }} + {{- if .Values.config.enableDefaultRegistryMutation }} + enableDefaultRegistryMutation: {{ .Values.config.enableDefaultRegistryMutation | quote }} + {{- end }} # resource types to be skipped by kyverno policy engine {{- if .Values.config.resourceFilters }} resourceFilters: {{ include "kyverno.resourceFilters" . | quote }} diff --git a/charts/kyverno/values.yaml b/charts/kyverno/values.yaml index 0fe78a27c9..8625082797 100644 --- a/charts/kyverno/values.yaml +++ b/charts/kyverno/values.yaml @@ -304,6 +304,12 @@ excludeKyvernoNamespace: true resourceFiltersExcludeNamespaces: [] config: + # -- The registry hostname used for the image mutation. + defaultRegistry: docker.io + + # -- Enable registry mutation for container images. Enabled by default. + enableDefaultRegistryMutation: true + # -- Resource types to be skipped by the Kyverno policy engine. # Make sure to surround each entry in quotes so that it doesn't get parsed as a nested YAML list. # These are joined together without spaces, run through `tpl`, and the result is set in the config map. diff --git a/cmd/cleanup-controller/handlers/cleanup/handlers.go b/cmd/cleanup-controller/handlers/cleanup/handlers.go index e18117579d..82d2bf414c 100644 --- a/cmd/cleanup-controller/handlers/cleanup/handlers.go +++ b/cmd/cleanup-controller/handlers/cleanup/handlers.go @@ -8,6 +8,7 @@ import ( kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1" kyvernov2alpha1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v2alpha1" "github.com/kyverno/kyverno/pkg/clients/dclient" + "github.com/kyverno/kyverno/pkg/config" enginecontext "github.com/kyverno/kyverno/pkg/engine/context" controllerutils "github.com/kyverno/kyverno/pkg/utils/controller" match "github.com/kyverno/kyverno/pkg/utils/match" @@ -38,7 +39,7 @@ func New( } } -func (h *handlers) Cleanup(ctx context.Context, logger logr.Logger, name string, _ time.Time) error { +func (h *handlers) Cleanup(ctx context.Context, logger logr.Logger, name string, _ time.Time, cfg config.Configuration) error { logger.Info("cleaning up...") defer logger.Info("done") namespace, name, err := cache.SplitMetaNamespaceKey(name) @@ -49,7 +50,7 @@ func (h *handlers) Cleanup(ctx context.Context, logger logr.Logger, name string, if err != nil { return err } - return h.executePolicy(ctx, logger, policy) + return h.executePolicy(ctx, logger, policy, cfg) } func (h *handlers) lookupPolicy(namespace, name string) (kyvernov2alpha1.CleanupPolicyInterface, error) { @@ -60,7 +61,7 @@ func (h *handlers) lookupPolicy(namespace, name string) (kyvernov2alpha1.Cleanup } } -func (h *handlers) executePolicy(ctx context.Context, logger logr.Logger, policy kyvernov2alpha1.CleanupPolicyInterface) error { +func (h *handlers) executePolicy(ctx context.Context, logger logr.Logger, policy kyvernov2alpha1.CleanupPolicyInterface, cfg config.Configuration) error { spec := policy.GetSpec() kinds := sets.New(spec.MatchResources.GetKinds()...) debug := logger.V(5) @@ -120,7 +121,7 @@ func (h *handlers) executePolicy(ctx context.Context, logger logr.Logger, policy errs = append(errs, err) continue } - if err := enginectx.AddImageInfos(&resource); err != nil { + if err := enginectx.AddImageInfos(&resource, cfg); err != nil { debug.Error(err, "failed to add image infos in context") errs = append(errs, err) continue diff --git a/cmd/cleanup-controller/main.go b/cmd/cleanup-controller/main.go index 59913eacc6..27abd745a9 100644 --- a/cmd/cleanup-controller/main.go +++ b/cmd/cleanup-controller/main.go @@ -207,6 +207,7 @@ func main() { DumpPayload: dumpPayload, }, probes{}, + config.NewDefaultConfiguration(), ) // start server server.Run(ctx.Done()) diff --git a/cmd/cleanup-controller/server.go b/cmd/cleanup-controller/server.go index 0bd6230379..f4b2027b5d 100644 --- a/cmd/cleanup-controller/server.go +++ b/cmd/cleanup-controller/server.go @@ -32,7 +32,7 @@ type server struct { type ( TlsProvider = func() ([]byte, []byte, error) ValidationHandler = func(context.Context, logr.Logger, *admissionv1.AdmissionRequest, time.Time) *admissionv1.AdmissionResponse - CleanupHandler = func(context.Context, logr.Logger, string, time.Time) error + CleanupHandler = func(context.Context, logr.Logger, string, time.Time, config.Configuration) error ) type Probes interface { @@ -48,13 +48,14 @@ func NewServer( metricsConfig metrics.MetricsConfigManager, debugModeOpts webhooks.DebugModeOptions, probes Probes, + cfg config.Configuration, ) Server { policyLogger := logging.WithName("cleanup-policy") cleanupLogger := logging.WithName("cleanup") cleanupHandlerFunc := func(w http.ResponseWriter, r *http.Request) { policy := r.URL.Query().Get("policy") logger := cleanupLogger.WithValues("policy", policy) - err := cleanupHandler(r.Context(), logger, policy, time.Now()) + err := cleanupHandler(r.Context(), logger, policy, time.Now(), cfg) if err == nil { w.WriteHeader(http.StatusOK) } else { diff --git a/cmd/cli/kubectl-kyverno/utils/common/common.go b/cmd/cli/kubectl-kyverno/utils/common/common.go index ad7dc27813..ecd09836ff 100644 --- a/cmd/cli/kubectl-kyverno/utils/common/common.go +++ b/cmd/cli/kubectl-kyverno/utils/common/common.go @@ -21,6 +21,7 @@ import ( "github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/background/generate" "github.com/kyverno/kyverno/pkg/clients/dclient" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine" engineContext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" @@ -452,7 +453,8 @@ OuterLoop: } } - if err := ctx.AddImageInfos(c.Resource); err != nil { + cfg := config.NewDefaultConfiguration() + if err := ctx.AddImageInfos(c.Resource, cfg); err != nil { if err != nil { log.Log.Error(err, "failed to add image variables to context") } @@ -511,7 +513,7 @@ OuterLoop: var info Info var validateResponse *response.EngineResponse if policyHasValidate { - validateResponse = engine.Validate(context.Background(), registryclient.NewOrDie(), policyContext) + validateResponse = engine.Validate(context.Background(), registryclient.NewOrDie(), policyContext, cfg) info = ProcessValidateEngineResponse(c.Policy, validateResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn) } @@ -519,7 +521,7 @@ OuterLoop: engineResponses = append(engineResponses, validateResponse) } - verifyImageResponse, _ := engine.VerifyAndPatchImages(context.Background(), registryclient.NewOrDie(), policyContext) + verifyImageResponse, _ := engine.VerifyAndPatchImages(context.Background(), registryclient.NewOrDie(), policyContext, cfg) if verifyImageResponse != nil && !verifyImageResponse.IsEmpty() { engineResponses = append(engineResponses, verifyImageResponse) info = ProcessValidateEngineResponse(c.Policy, verifyImageResponse, resPath, c.Rc, c.PolicyReport, c.AuditWarn) diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index 74aea81c14..8b69eab63c 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -170,6 +170,7 @@ func createReportControllers( kyvernoInformer kyvernoinformer.SharedInformerFactory, configMapResolver resolvers.ConfigmapResolver, backgroundScanInterval time.Duration, + configuration config.Configuration, ) ([]internal.Controller, func(context.Context) error) { var ctrls []internal.Controller var warmups []func(context.Context) error @@ -225,6 +226,7 @@ func createReportControllers( resourceReportController, configMapResolver, backgroundScanInterval, + configuration, ), backgroundScanWorkers, )) @@ -340,6 +342,7 @@ func createrLeaderControllers( kyvernoInformer, configMapResolver, backgroundScanInterval, + configuration, ) return append( []internal.Controller{ diff --git a/config/install.yaml b/config/install.yaml index c134ea8ccd..0ac111c26c 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -46,6 +46,8 @@ metadata: name: kyverno namespace: kyverno data: + defaultRegistry: "docker.io" + enableDefaultRegistryMutation: "true" # resource types to be skipped by kyverno policy engine resourceFilters: "[*,kyverno,*][Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][SelfSubjectAccessReview,*,*][Binding,*,*][ReplicaSet,*,*][AdmissionReport,*,*][ClusterAdmissionReport,*,*][BackgroundScanReport,*,*][ClusterBackgroundScanReport,*,*][ClusterRole,*,kyverno:*][ClusterRoleBinding,*,kyverno:*][ServiceAccount,kyverno,kyverno][ConfigMap,kyverno,kyverno][ConfigMap,kyverno,kyverno-metrics][Deployment,kyverno,kyverno][Job,kyverno,kyverno-hook-pre-delete][NetworkPolicy,kyverno,kyverno][PodDisruptionBudget,kyverno,kyverno][Role,kyverno,kyverno:*][RoleBinding,kyverno,kyverno:*][Secret,kyverno,kyverno-svc.kyverno.svc.*][Service,kyverno,kyverno-svc][Service,kyverno,kyverno-svc-metrics][ServiceMonitor,kyverno,kyverno-svc-service-monitor][Pod,kyverno,kyverno-test]" webhooks: '[{"namespaceSelector": {"matchExpressions": [{"key":"kubernetes.io/metadata.name","operator":"NotIn","values":["kyverno"]}]}}]' diff --git a/pkg/background/common/context.go b/pkg/background/common/context.go index 834063dc1b..c90977472a 100644 --- a/pkg/background/common/context.go +++ b/pkg/background/common/context.go @@ -75,7 +75,7 @@ func NewBackgroundContext(dclient dclient.Interface, ur *kyvernov1beta1.UpdateRe return nil, false, errors.Wrapf(err, "failed to load UserInfo in context") } - if err := ctx.AddImageInfos(trigger); err != nil { + if err := ctx.AddImageInfos(trigger, cfg); err != nil { logger.Error(err, "unable to add image info to variables context") } diff --git a/pkg/config/config.go b/pkg/config/config.go index 97637212a9..0e004ddd8b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -5,6 +5,7 @@ import ( "strconv" "sync" + valid "github.com/asaskevich/govalidator" osutils "github.com/kyverno/kyverno/pkg/utils/os" wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard" corev1 "k8s.io/api/core/v1" @@ -130,6 +131,10 @@ func KyvernoConfigMapName() string { // Configuration to be used by consumer to check filters type Configuration interface { + // GetDefaultRegistry return default image registry + GetDefaultRegistry() string + // GetEnableDefaultRegistryMutation return if should mutate image registry + GetEnableDefaultRegistryMutation() bool // ToFilter checks if the given resource is set to be filtered in the configuration ToFilter(kind, namespace, name string) bool // GetExcludeGroupRole return exclude roles @@ -148,18 +153,22 @@ type Configuration interface { // configuration stores the configuration type configuration struct { - mux sync.RWMutex - filters []filter - excludeGroupRole []string - excludeUsername []string - webhooks []WebhookConfig - generateSuccessEvents bool + defaultRegistry string + enableDefaultRegistryMutation bool + excludeGroupRole []string + excludeUsername []string + filters []filter + generateSuccessEvents bool + mux sync.RWMutex + webhooks []WebhookConfig } // NewDefaultConfiguration ... func NewDefaultConfiguration() *configuration { return &configuration{ - excludeGroupRole: defaultExcludeGroupRole, + defaultRegistry: "docker.io", + enableDefaultRegistryMutation: true, + excludeGroupRole: defaultExcludeGroupRole, } } @@ -199,6 +208,18 @@ func (cd *configuration) GetExcludeGroupRole() []string { return cd.excludeGroupRole } +func (cd *configuration) GetDefaultRegistry() string { + cd.mux.RLock() + defer cd.mux.RUnlock() + return cd.defaultRegistry +} + +func (cd *configuration) GetEnableDefaultRegistryMutation() bool { + cd.mux.RLock() + defer cd.mux.RUnlock() + return cd.enableDefaultRegistryMutation +} + func (cd *configuration) GetExcludeUsername() []string { cd.mux.RLock() defer cd.mux.RUnlock() @@ -250,6 +271,28 @@ func (cd *configuration) load(cm *corev1.ConfigMap) { cd.webhooks = nil // load filters cd.filters = parseKinds(cm.Data["resourceFilters"]) + newDefaultRegistry, ok := cm.Data["defaultRegistry"] + if !ok { + logger.V(4).Info("configuration: No defaultRegistry defined in ConfigMap") + } else { + if valid.IsDNSName(newDefaultRegistry) { + logger.V(4).Info("Updated defaultRegistry config parameter.", "oldDefaultRegistry", cd.defaultRegistry, "newDefaultRegistry", newDefaultRegistry) + cd.defaultRegistry = newDefaultRegistry + } else { + logger.V(4).Info("defaultRegistry didn't change because the provided config value isn't a valid DNS hostname") + } + } + enableDefaultRegistryMutation, ok := cm.Data["enableDefaultRegistryMutation"] + if !ok { + logger.V(4).Info("configuration: No enableDefaultRegistryMutation defined in ConfigMap") + } else { + newEnableDefaultRegistryMutation, err := strconv.ParseBool(enableDefaultRegistryMutation) + if err != nil { + logger.V(4).Info("configuration: Invalid value for enableDefaultRegistryMutation defined in ConfigMap. enableDefaultRegistryMutation didn't change") + } + logger.V(4).Info("Updated enableDefaultRegistryMutation config parameter", "oldEnableDefaultRegistryMutation", cd.enableDefaultRegistryMutation, "newEnableDefaultRegistryMutation", newEnableDefaultRegistryMutation) + cd.enableDefaultRegistryMutation = newEnableDefaultRegistryMutation + } // load excludeGroupRole cd.excludeGroupRole = append(cd.excludeGroupRole, parseRbac(cm.Data["excludeGroupRole"])...) cd.excludeGroupRole = append(cd.excludeGroupRole, defaultExcludeGroupRole...) @@ -281,6 +324,8 @@ func (cd *configuration) unload() { cd.mux.Lock() defer cd.mux.Unlock() cd.filters = []filter{} + cd.defaultRegistry = "docker.io" + cd.enableDefaultRegistryMutation = true cd.excludeGroupRole = []string{} cd.excludeUsername = []string{} cd.generateSuccessEvents = false diff --git a/pkg/controllers/report/background/controller.go b/pkg/controllers/report/background/controller.go index 1a9db601c3..b3cd251468 100644 --- a/pkg/controllers/report/background/controller.go +++ b/pkg/controllers/report/background/controller.go @@ -12,6 +12,7 @@ import ( kyvernov1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1" kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1" "github.com/kyverno/kyverno/pkg/clients/dclient" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/controllers" "github.com/kyverno/kyverno/pkg/controllers/report/resource" "github.com/kyverno/kyverno/pkg/controllers/report/utils" @@ -62,6 +63,8 @@ type controller struct { metadataCache resource.MetadataCache informerCacheResolvers resolvers.ConfigmapResolver forceDelay time.Duration + + cfg config.Configuration } func NewController( @@ -75,6 +78,7 @@ func NewController( metadataCache resource.MetadataCache, informerCacheResolvers resolvers.ConfigmapResolver, forceDelay time.Duration, + cfg config.Configuration, ) controllers.Controller { bgscanr := metadataFactory.ForResource(kyvernov1alpha2.SchemeGroupVersion.WithResource("backgroundscanreports")) cbgscanr := metadataFactory.ForResource(kyvernov1alpha2.SchemeGroupVersion.WithResource("clusterbackgroundscanreports")) @@ -94,6 +98,7 @@ func NewController( metadataCache: metadataCache, informerCacheResolvers: informerCacheResolvers, forceDelay: forceDelay, + cfg: cfg, } controllerutils.AddEventHandlersT(polInformer.Informer(), c.addPolicy, c.updatePolicy, c.deletePolicy) controllerutils.AddEventHandlersT(cpolInformer.Informer(), c.addPolicy, c.updatePolicy, c.deletePolicy) @@ -239,7 +244,7 @@ func (c *controller) updateReport(ctx context.Context, meta metav1.Object, gvk s } // if the resource changed, we need to rebuild the report if force || !reportutils.CompareHash(meta, resource.Hash) { - scanner := utils.NewScanner(logger, c.client, c.rclient, c.informerCacheResolvers) + scanner := utils.NewScanner(logger, c.client, c.rclient, c.informerCacheResolvers, c.cfg) before, err := c.getReport(ctx, meta.GetNamespace(), meta.GetName()) if err != nil { return nil @@ -329,7 +334,7 @@ func (c *controller) updateReport(ctx context.Context, meta metav1.Object, gvk s } // creations if len(toCreate) > 0 { - scanner := utils.NewScanner(logger, c.client, c.rclient, c.informerCacheResolvers) + scanner := utils.NewScanner(logger, c.client, c.rclient, c.informerCacheResolvers, c.cfg) resource, err := c.client.GetResource(ctx, gvk.GroupVersion().String(), gvk.Kind, resource.Namespace, resource.Name) if err != nil { return err diff --git a/pkg/controllers/report/utils/scanner.go b/pkg/controllers/report/utils/scanner.go index 507ebf54d4..41a257095f 100644 --- a/pkg/controllers/report/utils/scanner.go +++ b/pkg/controllers/report/utils/scanner.go @@ -6,6 +6,7 @@ import ( "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/clients/dclient" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine" enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/context/resolvers" @@ -20,6 +21,7 @@ type scanner struct { client dclient.Interface rclient registryclient.Client informerCacheResolvers resolvers.ConfigmapResolver + cfg config.Configuration excludeGroupRole []string } @@ -32,12 +34,13 @@ type Scanner interface { ScanResource(context.Context, unstructured.Unstructured, map[string]string, ...kyvernov1.PolicyInterface) map[kyvernov1.PolicyInterface]ScanResult } -func NewScanner(logger logr.Logger, client dclient.Interface, rclient registryclient.Client, informerCacheResolvers resolvers.ConfigmapResolver, excludeGroupRole ...string) Scanner { +func NewScanner(logger logr.Logger, client dclient.Interface, rclient registryclient.Client, informerCacheResolvers resolvers.ConfigmapResolver, cfg config.Configuration, excludeGroupRole ...string) Scanner { return &scanner{ logger: logger, client: client, rclient: rclient, informerCacheResolvers: informerCacheResolvers, + cfg: cfg, excludeGroupRole: excludeGroupRole, } } @@ -77,7 +80,7 @@ func (s *scanner) validateResource(ctx context.Context, resource unstructured.Un if err := enginectx.AddNamespace(resource.GetNamespace()); err != nil { return nil, err } - if err := enginectx.AddImageInfos(&resource); err != nil { + if err := enginectx.AddImageInfos(&resource, s.cfg); err != nil { return nil, err } if err := enginectx.AddOperation("CREATE"); err != nil { @@ -90,7 +93,7 @@ func (s *scanner) validateResource(ctx context.Context, resource unstructured.Un WithNamespaceLabels(nsLabels). WithExcludeGroupRole(s.excludeGroupRole...). WithInformerCacheResolver(s.informerCacheResolvers) - return engine.Validate(ctx, s.rclient, policyCtx), nil + return engine.Validate(ctx, s.rclient, policyCtx, s.cfg), nil } func (s *scanner) validateImages(ctx context.Context, resource unstructured.Unstructured, nsLabels map[string]string, policy kyvernov1.PolicyInterface) (*response.EngineResponse, error) { @@ -101,7 +104,7 @@ func (s *scanner) validateImages(ctx context.Context, resource unstructured.Unst if err := enginectx.AddNamespace(resource.GetNamespace()); err != nil { return nil, err } - if err := enginectx.AddImageInfos(&resource); err != nil { + if err := enginectx.AddImageInfos(&resource, s.cfg); err != nil { return nil, err } if err := enginectx.AddOperation("CREATE"); err != nil { @@ -114,7 +117,7 @@ func (s *scanner) validateImages(ctx context.Context, resource unstructured.Unst WithNamespaceLabels(nsLabels). WithExcludeGroupRole(s.excludeGroupRole...). WithInformerCacheResolver(s.informerCacheResolvers) - response, _ := engine.VerifyAndPatchImages(ctx, s.rclient, policyCtx) + response, _ := engine.VerifyAndPatchImages(ctx, s.rclient, policyCtx, s.cfg) if len(response.PolicyResponse.Rules) > 0 { s.logger.Info("validateImages", "policy", policy, "response", response) } diff --git a/pkg/engine/context/context.go b/pkg/engine/context/context.go index fbf5db210c..8de8d462c0 100644 --- a/pkg/engine/context/context.go +++ b/pkg/engine/context/context.go @@ -9,6 +9,7 @@ import ( jsonpatch "github.com/evanphx/json-patch/v5" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/logging" apiutils "github.com/kyverno/kyverno/pkg/utils/api" "github.com/pkg/errors" @@ -69,17 +70,17 @@ type Interface interface { AddElement(data interface{}, index, nesting int) error // AddImageInfo adds image info to the context - AddImageInfo(info apiutils.ImageInfo) error + AddImageInfo(info apiutils.ImageInfo, cfg config.Configuration) error // AddImageInfos adds image infos to the context - AddImageInfos(resource *unstructured.Unstructured) error + AddImageInfos(resource *unstructured.Unstructured, cfg config.Configuration) error // ImageInfo returns image infos present in the context ImageInfo() map[string]map[string]apiutils.ImageInfo // GenerateCustomImageInfo returns image infos as defined by a custom image extraction config // and updates the context - GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kyvernov1.ImageExtractorConfigs) (map[string]map[string]apiutils.ImageInfo, error) + GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kyvernov1.ImageExtractorConfigs, cfg config.Configuration) (map[string]map[string]apiutils.ImageInfo, error) // Checkpoint creates a copy of the current internal state and pushes it into a stack of stored states. Checkpoint() @@ -252,7 +253,7 @@ func (ctx *context) AddElement(data interface{}, index, nesting int) error { return addToContext(ctx, data) } -func (ctx *context) AddImageInfo(info apiutils.ImageInfo) error { +func (ctx *context) AddImageInfo(info apiutils.ImageInfo, cfg config.Configuration) error { data := map[string]interface{}{ "reference": info.String(), "referenceWithTag": info.ReferenceWithTag(), @@ -265,8 +266,8 @@ func (ctx *context) AddImageInfo(info apiutils.ImageInfo) error { return addToContext(ctx, data, "image") } -func (ctx *context) AddImageInfos(resource *unstructured.Unstructured) error { - images, err := apiutils.ExtractImagesFromResource(*resource, nil) +func (ctx *context) AddImageInfos(resource *unstructured.Unstructured, cfg config.Configuration) error { + images, err := apiutils.ExtractImagesFromResource(*resource, nil, cfg) if err != nil { return err } @@ -279,8 +280,8 @@ func (ctx *context) AddImageInfos(resource *unstructured.Unstructured) error { return addToContext(ctx, images, "images") } -func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kyvernov1.ImageExtractorConfigs) (map[string]map[string]apiutils.ImageInfo, error) { - images, err := apiutils.ExtractImagesFromResource(*resource, imageExtractorConfigs) +func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kyvernov1.ImageExtractorConfigs, cfg config.Configuration) (map[string]map[string]apiutils.ImageInfo, error) { + images, err := apiutils.ExtractImagesFromResource(*resource, imageExtractorConfigs, cfg) if err != nil { return nil, errors.Wrapf(err, "failed to extract images") } diff --git a/pkg/engine/imageVerify.go b/pkg/engine/imageVerify.go index 3277a0e194..7b58baafca 100644 --- a/pkg/engine/imageVerify.go +++ b/pkg/engine/imageVerify.go @@ -12,6 +12,7 @@ import ( "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/autogen" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/cosign" enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" @@ -46,7 +47,7 @@ func getMatchingImages(images map[string]map[string]apiutils.ImageInfo, rule *ky return imageInfos, strings.Join(imageRefs, ",") } -func extractMatchingImages(policyContext *PolicyContext, rule *kyvernov1.Rule) ([]apiutils.ImageInfo, string, error) { +func extractMatchingImages(policyContext *PolicyContext, rule *kyvernov1.Rule, cfg config.Configuration) ([]apiutils.ImageInfo, string, error) { var ( images map[string]map[string]apiutils.ImageInfo err error @@ -54,7 +55,7 @@ func extractMatchingImages(policyContext *PolicyContext, rule *kyvernov1.Rule) ( images = policyContext.jsonContext.ImageInfo() if rule.ImageExtractors != nil { images, err = policyContext.jsonContext.GenerateCustomImageInfo( - &policyContext.newResource, rule.ImageExtractors) + &policyContext.newResource, rule.ImageExtractors, cfg) if err != nil { // if we get an error while generating custom images from image extractors, // don't check for matching images in imageExtractors @@ -69,6 +70,7 @@ func VerifyAndPatchImages( ctx context.Context, rclient registryclient.Client, policyContext *PolicyContext, + cfg config.Configuration, ) (*response.EngineResponse, *ImageVerificationMetadata) { resp := &response.EngineResponse{} @@ -117,7 +119,7 @@ func VerifyAndPatchImages( logger.V(3).Info("processing image verification rule", "ruleSelector", applyRules) - ruleImages, imageRefs, err := extractMatchingImages(policyContext, rule) + ruleImages, imageRefs, err := extractMatchingImages(policyContext, rule, cfg) if err != nil { appendResponse(resp, rule, fmt.Sprintf("failed to extract images: %s", err.Error()), response.RuleStatusError) return @@ -154,7 +156,7 @@ func VerifyAndPatchImages( } for _, imageVerify := range ruleCopy.VerifyImages { - iv.verify(ctx, imageVerify, ruleImages) + iv.verify(ctx, imageVerify, ruleImages, cfg) } }, ) @@ -205,7 +207,7 @@ type imageVerifier struct { // verify applies policy rules to each matching image. The policy rule results and annotation patches are // added to tme imageVerifier `resp` and `ivm` fields. -func (iv *imageVerifier) verify(ctx context.Context, imageVerify kyvernov1.ImageVerification, matchedImageInfos []apiutils.ImageInfo) { +func (iv *imageVerifier) verify(ctx context.Context, imageVerify kyvernov1.ImageVerification, matchedImageInfos []apiutils.ImageInfo, cfg config.Configuration) { // for backward compatibility imageVerify = *imageVerify.Convert() @@ -234,7 +236,7 @@ func (iv *imageVerifier) verify(ctx context.Context, imageVerify kyvernov1.Image continue } - ruleResp, digest := iv.verifyImage(ctx, imageVerify, imageInfo) + ruleResp, digest := iv.verifyImage(ctx, imageVerify, imageInfo, cfg) if imageVerify.MutateDigest { patch, retrievedDigest, err := iv.handleMutateDigest(ctx, digest, imageInfo) @@ -317,6 +319,7 @@ func (iv *imageVerifier) verifyImage( ctx context.Context, imageVerify kyvernov1.ImageVerification, imageInfo apiutils.ImageInfo, + cfg config.Configuration, ) (*response.RuleResponse, string) { if len(imageVerify.Attestors) <= 0 && len(imageVerify.Attestations) <= 0 { return nil, "" @@ -326,7 +329,7 @@ func (iv *imageVerifier) verifyImage( iv.logger.V(2).Info("verifying image signatures", "image", image, "attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations)) - if err := iv.policyContext.jsonContext.AddImageInfo(imageInfo); err != nil { + if err := iv.policyContext.jsonContext.AddImageInfo(imageInfo, cfg); err != nil { iv.logger.Error(err, "failed to add image to context") msg := fmt.Sprintf("failed to add image to context %s: %s", image, err.Error()) return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusError), "" diff --git a/pkg/engine/imageVerifyValidate.go b/pkg/engine/imageVerifyValidate.go index d1d8515b2d..37de033877 100644 --- a/pkg/engine/imageVerifyValidate.go +++ b/pkg/engine/imageVerifyValidate.go @@ -8,6 +8,7 @@ import ( "github.com/go-logr/logr" gojmespath "github.com/jmespath/go-jmespath" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/registryclient" apiutils "github.com/kyverno/kyverno/pkg/utils/api" @@ -15,13 +16,13 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -func processImageValidationRule(ctx context.Context, log logr.Logger, rclient registryclient.Client, enginectx *PolicyContext, rule *kyvernov1.Rule) *response.RuleResponse { +func processImageValidationRule(ctx context.Context, log logr.Logger, rclient registryclient.Client, enginectx *PolicyContext, rule *kyvernov1.Rule, cfg config.Configuration) *response.RuleResponse { if isDeleteRequest(enginectx) { return nil } log = log.WithValues("rule", rule.Name) - matchingImages, _, err := extractMatchingImages(enginectx, rule) + matchingImages, _, err := extractMatchingImages(enginectx, rule, cfg) if err != nil { return ruleResponse(*rule, response.Validation, err.Error(), response.RuleStatusError) } diff --git a/pkg/engine/imageVerify_test.go b/pkg/engine/imageVerify_test.go index e901b84794..7f5aa5ed65 100644 --- a/pkg/engine/imageVerify_test.go +++ b/pkg/engine/imageVerify_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/registryclient" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -63,7 +64,7 @@ var testPolicyGood = `{ "key": "{{ repo.uri }}", "operator": "Equals", "value": "https://github.com/example/my-project" - }, + }, { "key": "{{ repo.branch }}", "operator": "Equals", @@ -112,7 +113,7 @@ var testPolicyBad = `{ "key": "{{ repo.uri }}", "operator": "Equals", "value": "https://github.com/example/my-project" - }, + }, { "key": "{{ repo.branch }}", "operator": "Equals", @@ -157,12 +158,14 @@ var signaturePayloads = [][]byte{ []byte(`{"critical":{"identity":{"docker-reference":"ghcr.io/kyverno/test-verify-image"},"image":{"docker-manifest-digest":"sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105"},"type":"cosign container image signature"},"optional":null}`), } +var cfg = config.NewDefaultConfiguration() + func Test_CosignMockAttest(t *testing.T) { policyContext := buildContext(t, testPolicyGood, testResource, "") err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", attestationPayloads) assert.NilError(t, err) - er, ivm := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + er, ivm := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(er.PolicyResponse.Rules), 1) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusPass, fmt.Sprintf("expected: %v, got: %v, failure: %v", @@ -176,7 +179,7 @@ func Test_CosignMockAttest_fail(t *testing.T) { err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", attestationPayloads) assert.NilError(t, err) - er, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + er, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(er.PolicyResponse.Rules), 1) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail) } @@ -209,7 +212,7 @@ func buildContext(t *testing.T, policy, resource string, oldResource string) *Po policyContext.oldResource = *oldResourceUnstructured } - if err := ctx.AddImageInfos(resourceUnstructured); err != nil { + if err := ctx.AddImageInfos(resourceUnstructured, cfg); err != nil { t.Errorf("unable to add image info to variables context: %v", err) t.Fail() } @@ -425,7 +428,7 @@ var ( func Test_ConfigMapMissingSuccess(t *testing.T) { policyContext := buildContext(t, testConfigMapMissing, testConfigMapMissingResource, "") cosign.ClearMock() - err, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + err, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(err.PolicyResponse.Rules), 1) assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusSkip, err.PolicyResponse.Rules[0].Message) } @@ -437,7 +440,7 @@ func Test_ConfigMapMissingFailure(t *testing.T) { assert.NilError(t, err) policyContext.informerCacheResolvers = resolver cosign.ClearMock() - resp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + resp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(resp.PolicyResponse.Rules), 1) assert.Equal(t, resp.PolicyResponse.Rules[0].Status, response.RuleStatusError, resp.PolicyResponse.Rules[0].Message) } @@ -446,7 +449,7 @@ func Test_SignatureGoodSigned(t *testing.T) { policyContext := buildContext(t, testSampleSingleKeyPolicy, testSampleResource, "") policyContext.policy.GetSpec().Rules[0].VerifyImages[0].MutateDigest = true cosign.ClearMock() - engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1) assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message) assert.Equal(t, len(engineResp.PolicyResponse.Rules[0].Patches), 1) @@ -458,7 +461,7 @@ func Test_SignatureUnsigned(t *testing.T) { cosign.ClearMock() unsigned := strings.Replace(testSampleResource, ":signed", ":unsigned", -1) policyContext := buildContext(t, testSampleSingleKeyPolicy, unsigned, "") - engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1) assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message) } @@ -467,7 +470,7 @@ func Test_SignatureWrongKey(t *testing.T) { cosign.ClearMock() otherKey := strings.Replace(testSampleResource, ":signed", ":signed-by-someone-else", -1) policyContext := buildContext(t, testSampleSingleKeyPolicy, otherKey, "") - engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1) assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message) } @@ -478,7 +481,7 @@ func Test_SignaturesMultiKey(t *testing.T) { policy = strings.Replace(policy, "KEY2", testVerifyImageKey, -1) policy = strings.Replace(policy, "COUNT", "0", -1) policyContext := buildContext(t, policy, testSampleResource, "") - engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1) assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message) } @@ -488,7 +491,7 @@ func Test_SignaturesMultiKeyFail(t *testing.T) { policy := strings.Replace(testSampleMultipleKeyPolicy, "KEY1", testVerifyImageKey, -1) policy = strings.Replace(policy, "COUNT", "0", -1) policyContext := buildContext(t, policy, testSampleResource, "") - engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1) assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message) } @@ -499,7 +502,7 @@ func Test_SignaturesMultiKeyOneGoodKey(t *testing.T) { policy = strings.Replace(policy, "KEY2", testOtherKey, -1) policy = strings.Replace(policy, "COUNT", "1", -1) policyContext := buildContext(t, policy, testSampleResource, "") - engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + engineResp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1) assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message) } @@ -510,7 +513,7 @@ func Test_SignaturesMultiKeyZeroGoodKey(t *testing.T) { policy = strings.Replace(policy, "KEY2", testOtherKey, -1) policy = strings.Replace(policy, "COUNT", "1", -1) policyContext := buildContext(t, policy, testSampleResource, "") - resp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + resp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(resp.PolicyResponse.Rules), 1) assert.Equal(t, resp.PolicyResponse.Rules[0].Status, response.RuleStatusFail, resp.PolicyResponse.Rules[0].Message) } @@ -526,14 +529,14 @@ func Test_RuleSelectorImageVerify(t *testing.T) { applyAll := kyverno.ApplyAll spec.ApplyRules = &applyAll - resp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + resp, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(resp.PolicyResponse.Rules), 2) assert.Equal(t, resp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, resp.PolicyResponse.Rules[0].Message) assert.Equal(t, resp.PolicyResponse.Rules[1].Status, response.RuleStatusFail, resp.PolicyResponse.Rules[1].Message) applyOne := kyverno.ApplyOne spec.ApplyRules = &applyOne - resp, _ = VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + resp, _ = VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(resp.PolicyResponse.Rules), 1) assert.Equal(t, resp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, resp.PolicyResponse.Rules[0].Message) } @@ -637,7 +640,7 @@ func Test_NestedAttestors(t *testing.T) { policy = strings.Replace(policy, "KEY2", testVerifyImageKey, -1) policy = strings.Replace(policy, "COUNT", "0", -1) policyContext := buildContext(t, policy, testSampleResource, "") - err, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + err, _ := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(err.PolicyResponse.Rules), 1) assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusPass) @@ -645,7 +648,7 @@ func Test_NestedAttestors(t *testing.T) { policy = strings.Replace(policy, "KEY2", testOtherKey, -1) policy = strings.Replace(policy, "COUNT", "0", -1) policyContext = buildContext(t, policy, testSampleResource, "") - err, _ = VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + err, _ = VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(err.PolicyResponse.Rules), 1) assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusFail) @@ -653,7 +656,7 @@ func Test_NestedAttestors(t *testing.T) { policy = strings.Replace(policy, "KEY2", testOtherKey, -1) policy = strings.Replace(policy, "COUNT", "1", -1) policyContext = buildContext(t, policy, testSampleResource, "") - err, _ = VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + err, _ = VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(err.PolicyResponse.Rules), 1) assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusPass) } @@ -746,7 +749,7 @@ func Test_MarkImageVerified(t *testing.T) { err := cosign.SetMock(image, attestationPayloads) assert.NilError(t, err) - engineResponse, verifiedImages := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + engineResponse, verifiedImages := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Assert(t, engineResponse != nil) assert.Equal(t, len(engineResponse.PolicyResponse.Rules), 1) assert.Equal(t, engineResponse.PolicyResponse.Rules[0].Status, response.RuleStatusPass) @@ -839,7 +842,7 @@ func Test_ParsePEMDelimited(t *testing.T) { err := cosign.SetMock(image, signaturePayloads) assert.NilError(t, err) - engineResponse, verifiedImages := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext) + engineResponse, verifiedImages := VerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Assert(t, engineResponse != nil) assert.Equal(t, len(engineResponse.PolicyResponse.Rules), 1) assert.Equal(t, engineResponse.PolicyResponse.Rules[0].Status, response.RuleStatusPass) diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go index 7eb5748c00..c9152b9109 100644 --- a/pkg/engine/mutation_test.go +++ b/pkg/engine/mutation_test.go @@ -369,7 +369,7 @@ func Test_chained_rules(t *testing.T) { newResource: *resource, } - err = ctx.AddImageInfos(resource) + err = ctx.AddImageInfos(resource, cfg) assert.NilError(t, err) err = enginecontext.MutateResourceWithImageInfo(resourceRaw, ctx) @@ -648,7 +648,7 @@ func Test_foreach(t *testing.T) { newResource: *resource, } - err = ctx.AddImageInfos(resource) + err = ctx.AddImageInfos(resource, cfg) assert.NilError(t, err) err = enginecontext.MutateResourceWithImageInfo(resourceRaw, ctx) @@ -755,7 +755,7 @@ func Test_foreach_element_mutation(t *testing.T) { newResource: *resource, } - err = ctx.AddImageInfos(resource) + err = ctx.AddImageInfos(resource, cfg) assert.NilError(t, err) err = enginecontext.MutateResourceWithImageInfo(resourceRaw, ctx) @@ -881,7 +881,7 @@ func Test_Container_InitContainer_foreach(t *testing.T) { newResource: *resource, } - err = ctx.AddImageInfos(resource) + err = ctx.AddImageInfos(resource, cfg) assert.NilError(t, err) err = enginecontext.MutateResourceWithImageInfo(resourceRaw, ctx) @@ -1031,7 +1031,7 @@ func testApplyPolicyToResource(t *testing.T, policyRaw, resourceRaw []byte) *res newResource: *resource, } - err = ctx.AddImageInfos(resource) + err = ctx.AddImageInfos(resource, cfg) assert.NilError(t, err) err = enginecontext.MutateResourceWithImageInfo(resourceRaw, ctx) @@ -1646,7 +1646,7 @@ func Test_RuleSelectorMutate(t *testing.T) { } } } - } + } ] } }`) diff --git a/pkg/engine/policyContext.go b/pkg/engine/policyContext.go index ff05b17d46..fb9f74f848 100644 --- a/pkg/engine/policyContext.go +++ b/pkg/engine/policyContext.go @@ -259,7 +259,7 @@ func NewPolicyContextFromAdmissionRequest( if err != nil { return nil, errors.Wrap(err, "failed to parse resource") } - if err := ctx.AddImageInfos(&newResource); err != nil { + if err := ctx.AddImageInfos(&newResource, configuration); err != nil { return nil, errors.Wrap(err, "failed to add image information to the policy rule context") } requestResource := request.RequestResource.DeepCopy() diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 103f7b78ad..3713495b00 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -14,6 +14,7 @@ import ( kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store" "github.com/kyverno/kyverno/pkg/autogen" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine/common" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/engine/validate" @@ -37,7 +38,7 @@ import ( ) // Validate applies validation rules from policy on the resource -func Validate(ctx context.Context, rclient registryclient.Client, policyContext *PolicyContext) (resp *response.EngineResponse) { +func Validate(ctx context.Context, rclient registryclient.Client, policyContext *PolicyContext, cfg config.Configuration) (resp *response.EngineResponse) { resp = &response.EngineResponse{} startTime := time.Now() @@ -48,7 +49,7 @@ func Validate(ctx context.Context, rclient registryclient.Client, policyContext logger.V(4).Info("finished policy processing", "processingTime", resp.PolicyResponse.ProcessingTime.String(), "validationRulesApplied", resp.PolicyResponse.RulesAppliedCount) }() - resp = validateResource(ctx, logger, rclient, policyContext) + resp = validateResource(ctx, logger, rclient, policyContext, cfg) return } @@ -95,7 +96,7 @@ func buildResponse(ctx *PolicyContext, resp *response.EngineResponse, startTime resp.PolicyResponse.PolicyExecutionTimestamp = startTime.Unix() } -func validateResource(ctx context.Context, log logr.Logger, rclient registryclient.Client, enginectx *PolicyContext) *response.EngineResponse { +func validateResource(ctx context.Context, log logr.Logger, rclient registryclient.Client, enginectx *PolicyContext, cfg config.Configuration) *response.EngineResponse { resp := &response.EngineResponse{} enginectx.jsonContext.Checkpoint() @@ -145,7 +146,7 @@ func validateResource(ctx context.Context, log logr.Logger, rclient registryclie if hasValidate && !hasYAMLSignatureVerify { return processValidationRule(ctx, log, rclient, enginectx, rule) } else if hasValidateImage { - return processImageValidationRule(ctx, log, rclient, enginectx, rule) + return processImageValidationRule(ctx, log, rclient, enginectx, rule, cfg) } else if hasYAMLSignatureVerify { return processYAMLValidationRule(log, enginectx, rule) } diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go index 802ac6a1a0..b220a32099 100644 --- a/pkg/engine/validation_test.go +++ b/pkg/engine/validation_test.go @@ -133,7 +133,7 @@ func TestValidate_image_tag_fail(t *testing.T) { "validation error: imagePullPolicy 'Always' required with tag 'latest'. rule validate-latest failed at path /spec/containers/0/imagePullPolicy/", } - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) } @@ -233,7 +233,7 @@ func TestValidate_image_tag_pass(t *testing.T) { "validation rule 'validate-tag' passed.", "validation rule 'validate-latest' passed.", } - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) } @@ -307,7 +307,7 @@ func TestValidate_Fail_anyPattern(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) assert.Assert(t, !er.IsSuccessful()) msgs := []string{"validation error: A namespace is required. rule check-default-namespace[0] failed at path /metadata/namespace/ rule check-default-namespace[1] failed at path /metadata/namespace/"} @@ -390,7 +390,7 @@ func TestValidate_host_network_port(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation error: Host network and port are not allowed. rule validate-host-network-port failed at path /spec/containers/0/ports/0/hostPort/"} for index, r := range er.PolicyResponse.Rules { @@ -480,7 +480,7 @@ func TestValidate_anchor_arraymap_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation rule 'validate-host-path' passed."} for index, r := range er.PolicyResponse.Rules { @@ -568,7 +568,7 @@ func TestValidate_anchor_arraymap_fail(t *testing.T) { assert.NilError(t, err) resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation error: Host path '/var/lib/' is not allowed. rule validate-host-path failed at path /spec/volumes/0/hostPath/path/"} for index, r := range er.PolicyResponse.Rules { @@ -638,7 +638,7 @@ func TestValidate_anchor_map_notfound(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { @@ -711,7 +711,7 @@ func TestValidate_anchor_map_found_valid(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { @@ -785,7 +785,7 @@ func TestValidate_inequality_List_Processing(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { @@ -865,7 +865,7 @@ func TestValidate_inequality_List_ProcessingBrackets(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation rule 'pod rule 2' passed."} for index, r := range er.PolicyResponse.Rules { @@ -939,7 +939,7 @@ func TestValidate_anchor_map_found_invalid(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation error: pod: validate run as non root user. rule pod rule 2 failed at path /spec/securityContext/runAsNonRoot/"} for index, r := range er.PolicyResponse.Rules { @@ -1014,7 +1014,7 @@ func TestValidate_AnchorList_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation rule 'pod image rule' passed."} for index, r := range er.PolicyResponse.Rules { @@ -1089,7 +1089,7 @@ func TestValidate_AnchorList_fail(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) assert.Assert(t, !er.IsSuccessful()) } @@ -1159,7 +1159,7 @@ func TestValidate_existenceAnchor_fail(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) assert.Assert(t, !er.IsSuccessful()) } @@ -1229,7 +1229,7 @@ func TestValidate_existenceAnchor_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation rule 'pod image rule' passed."} for index, r := range er.PolicyResponse.Rules { @@ -1317,7 +1317,7 @@ func TestValidate_negationAnchor_deny(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation error: Host path is not allowed. rule validate-host-path failed at path /spec/volumes/0/hostPath/"} for index, r := range er.PolicyResponse.Rules { @@ -1404,7 +1404,7 @@ func TestValidate_negationAnchor_pass(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) msgs := []string{"validation rule 'validate-host-path' passed."} for index, r := range er.PolicyResponse.Rules { @@ -1481,7 +1481,7 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) { jsonContext: ctx, newResource: *resourceUnstructured, } - er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext) + er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(er.PolicyResponse.Rules), 1) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusError) @@ -1575,7 +1575,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSu jsonContext: ctx, newResource: *resourceUnstructured, } - er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext) + er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(er.PolicyResponse.Rules), 1) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusError) @@ -1637,7 +1637,7 @@ func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) { jsonContext: ctx, newResource: *resourceUnstructured, } - er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext) + er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail) assert.Equal(t, er.PolicyResponse.Rules[0].Message, "validation error: rule not-operator-with-variable-should-alway-fail-validation failed at path /spec/content/") } @@ -1729,7 +1729,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test jsonContext: ctx, newResource: *resourceUnstructured, } - er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext) + er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, len(er.PolicyResponse.Rules), 1) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusError) @@ -1823,7 +1823,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter jsonContext: ctx, newResource: *resourceUnstructured, } - er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext) + er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail) assert.Equal(t, er.PolicyResponse.Rules[0].Message, @@ -1929,7 +1929,7 @@ func Test_VariableSubstitutionValidate_VariablesInMessageAreResolved(t *testing. jsonContext: ctx, newResource: *resourceUnstructured, } - er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext) + er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail) assert.Equal(t, er.PolicyResponse.Rules[0].Message, "The animal cow is not in the allowed list of animals.") } @@ -1983,7 +1983,7 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) { jsonContext: ctx, newResource: *resourceUnstructured, } - er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext) + er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) for i, rule := range er.PolicyResponse.Rules { assert.Equal(t, er.PolicyResponse.Rules[i].Status, test.expectedResults[i], "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedResults[i].String(), er.PolicyResponse.Rules[i].Status.String()) @@ -2149,7 +2149,7 @@ func executeTest(t *testing.T, test testCase) { jsonContext: ctx, } - resp := Validate(context.TODO(), registryclient.NewOrDie(), pc) + resp := Validate(context.TODO(), registryclient.NewOrDie(), pc, cfg) if resp.IsSuccessful() && test.requestDenied { t.Errorf("Testcase has failed, policy: %v", policy.Name) } @@ -2248,7 +2248,7 @@ func TestValidate_context_variable_substitution_CLI(t *testing.T) { msgs := []string{ "restrict pod counts to be no more than 10 on node minikube", } - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) for index, r := range er.PolicyResponse.Rules { assert.Equal(t, r.Message, msgs[index]) } @@ -2337,7 +2337,7 @@ func Test_EmptyStringInDenyCondition(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: ctx}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: ctx}, cfg) assert.Assert(t, !er.IsSuccessful()) } @@ -2426,7 +2426,7 @@ func Test_StringInDenyCondition(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: ctx}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: ctx}, cfg) assert.Assert(t, er.IsSuccessful()) } @@ -3105,7 +3105,7 @@ func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string, jsonContext: ctx, newResource: *resourceUnstructured, } - er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext) + er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) assert.Equal(t, er.PolicyResponse.Rules[0].Status, status) if msg != "" { @@ -3169,7 +3169,7 @@ func Test_delete_ignore_pattern(t *testing.T) { jsonContext: ctx, newResource: *resourceUnstructured, } - engineResponseCreate := Validate(context.TODO(), registryclient.NewOrDie(), policyContextCreate) + engineResponseCreate := Validate(context.TODO(), registryclient.NewOrDie(), policyContextCreate, cfg) assert.Equal(t, len(engineResponseCreate.PolicyResponse.Rules), 1) assert.Equal(t, engineResponseCreate.PolicyResponse.Rules[0].Status, response.RuleStatusFail) @@ -3178,7 +3178,7 @@ func Test_delete_ignore_pattern(t *testing.T) { jsonContext: ctx, oldResource: *resourceUnstructured, } - engineResponseDelete := Validate(context.TODO(), registryclient.NewOrDie(), policyContextDelete) + engineResponseDelete := Validate(context.TODO(), registryclient.NewOrDie(), policyContextDelete, cfg) assert.Equal(t, len(engineResponseDelete.PolicyResponse.Rules), 0) } @@ -3237,7 +3237,7 @@ func Test_ValidatePattern_anyPattern(t *testing.T) { resourceUnstructured, err := utils.ConvertToUnstructured(tc.rawResource) assert.NilError(t, err) - er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}) + er := Validate(context.TODO(), registryclient.NewOrDie(), &PolicyContext{policy: &policy, newResource: *resourceUnstructured, jsonContext: enginecontext.NewContext()}, cfg) if tc.expectedFailed { assert.Assert(t, er.IsFailed()) } else if tc.expectedSkipped { diff --git a/pkg/policy/apply.go b/pkg/policy/apply.go index 4796d5c390..efdbfb4ba4 100644 --- a/pkg/policy/apply.go +++ b/pkg/policy/apply.go @@ -11,6 +11,7 @@ import ( "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/clients/dclient" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine" enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/context/resolvers" @@ -31,6 +32,7 @@ func applyPolicy( rclient registryclient.Client, informerCacheResolvers resolvers.ConfigmapResolver, namespaceLabels map[string]string, + cfg config.Configuration, ) (responses []*response.EngineResponse) { startTime := time.Now() defer func() { @@ -61,7 +63,7 @@ func applyPolicy( logger.Error(err, "failed to add namespace to ctx") } - if err := ctx.AddImageInfos(&resource); err != nil { + if err := ctx.AddImageInfos(&resource, cfg); err != nil { logger.Error(err, "unable to add image info to variables context") } @@ -82,7 +84,7 @@ func applyPolicy( WithExcludeGroupRole(excludeGroupRole...). WithInformerCacheResolver(informerCacheResolvers) - engineResponseValidation = engine.Validate(context.TODO(), rclient, policyCtx) + engineResponseValidation = engine.Validate(context.TODO(), rclient, policyCtx, cfg) engineResponses = append(engineResponses, mergeRuleRespose(engineResponseMutation, engineResponseValidation)) return engineResponses diff --git a/pkg/policy/policy_controller.go b/pkg/policy/policy_controller.go index f7963f6fd9..0adcbb808e 100644 --- a/pkg/policy/policy_controller.go +++ b/pkg/policy/policy_controller.go @@ -460,7 +460,7 @@ func (pc *PolicyController) applyPolicy(policy kyvernov1.PolicyInterface, resour } namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(resource.GetKind(), resource.GetNamespace(), pc.nsLister, logger) - engineResponse := applyPolicy(policy, resource, logger, pc.configHandler.GetExcludeGroupRole(), pc.client, pc.rclient, pc.informerCacheResolvers, namespaceLabels) + engineResponse := applyPolicy(policy, resource, logger, pc.configHandler.GetExcludeGroupRole(), pc.client, pc.rclient, pc.informerCacheResolvers, namespaceLabels, pc.configHandler) engineResponses = append(engineResponses, engineResponse...) // post-processing, register the resource as processed diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index 67617a8eb8..638e21ac93 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -97,7 +97,7 @@ func validateJSONPatch(patch string, ruleIdx int) error { for _, operation := range decodedPatch { op := operation.Kind() if op != "add" && op != "remove" && op != "replace" { - return fmt.Errorf("Unexpected kind: spec.rules[%d]: %s", ruleIdx, op) + return fmt.Errorf("unexpected kind: spec.rules[%d]: %s", ruleIdx, op) } v, _ := operation.ValueInterface() if v != nil { diff --git a/pkg/policy/validate_test.go b/pkg/policy/validate_test.go index a5861fe464..d7f6883706 100644 --- a/pkg/policy/validate_test.go +++ b/pkg/policy/validate_test.go @@ -1732,7 +1732,7 @@ func Test_ValidateJSON6902(t *testing.T) { op: addition value: "nginx"` err := validateJSONPatch(patch, 0) - assert.Error(t, err, "Unexpected kind: spec.rules[0]: addition") + assert.Error(t, err, "unexpected kind: spec.rules[0]: addition") patch = `- path: "/metadata/labels/img" op: add diff --git a/pkg/testrunner/scenario.go b/pkg/testrunner/scenario.go index 41ae1222bc..681af296a6 100644 --- a/pkg/testrunner/scenario.go +++ b/pkg/testrunner/scenario.go @@ -13,6 +13,7 @@ import ( kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/clients/dclient" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/registryclient" @@ -158,7 +159,8 @@ func runTestCase(t *testing.T, tc TestCase) bool { policyContext = policyContext.WithNewResource(*resource) - er = engine.Validate(context.TODO(), registryclient.NewOrDie(), policyContext) + cfg := config.NewDefaultConfiguration() + er = engine.Validate(context.TODO(), registryclient.NewOrDie(), policyContext, cfg) t.Log("---Validation---") validateResponse(t, er.PolicyResponse, tc.Expected.Validation.PolicyResponse) diff --git a/pkg/utils/api/image.go b/pkg/utils/api/image.go index 2dab03938d..14dcda7c5b 100644 --- a/pkg/utils/api/image.go +++ b/pkg/utils/api/image.go @@ -6,6 +6,7 @@ import ( "strings" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/config" imageutils "github.com/kyverno/kyverno/pkg/utils/image" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -39,15 +40,15 @@ type imageExtractor struct { Name string } -func (i *imageExtractor) ExtractFromResource(resource interface{}) (map[string]ImageInfo, error) { +func (i *imageExtractor) ExtractFromResource(resource interface{}, cfg config.Configuration) (map[string]ImageInfo, error) { imageInfo := map[string]ImageInfo{} - if err := extract(resource, []string{}, i.Key, i.Value, i.Fields, &imageInfo); err != nil { + if err := extract(resource, []string{}, i.Key, i.Value, i.Fields, &imageInfo, cfg); err != nil { return nil, err } return imageInfo, nil } -func extract(obj interface{}, path []string, keyPath, valuePath string, fields []string, imageInfos *map[string]ImageInfo) error { +func extract(obj interface{}, path []string, keyPath, valuePath string, fields []string, imageInfos *map[string]ImageInfo, cfg config.Configuration) error { if obj == nil { return nil } @@ -55,13 +56,13 @@ func extract(obj interface{}, path []string, keyPath, valuePath string, fields [ switch typedObj := obj.(type) { case []interface{}: for i, v := range typedObj { - if err := extract(v, append(path, strconv.Itoa(i)), keyPath, valuePath, fields[1:], imageInfos); err != nil { + if err := extract(v, append(path, strconv.Itoa(i)), keyPath, valuePath, fields[1:], imageInfos, cfg); err != nil { return err } } case map[string]interface{}: for i, v := range typedObj { - if err := extract(v, append(path, i), keyPath, valuePath, fields[1:], imageInfos); err != nil { + if err := extract(v, append(path, i), keyPath, valuePath, fields[1:], imageInfos, cfg); err != nil { return err } } @@ -89,7 +90,7 @@ func extract(obj interface{}, path []string, keyPath, valuePath string, fields [ if !ok { return fmt.Errorf("invalid value") } - if imageInfo, err := imageutils.GetImageInfo(value); err != nil { + if imageInfo, err := imageutils.GetImageInfo(value, cfg); err != nil { return fmt.Errorf("invalid image %s", value) } else { (*imageInfos)[key] = ImageInfo{*imageInfo, pointer} @@ -98,7 +99,7 @@ func extract(obj interface{}, path []string, keyPath, valuePath string, fields [ } currentPath := fields[0] - return extract(output[currentPath], append(path, currentPath), keyPath, valuePath, fields[1:], imageInfos) + return extract(output[currentPath], append(path, currentPath), keyPath, valuePath, fields[1:], imageInfos, cfg) } func BuildStandardExtractors(tags ...string) []imageExtractor { @@ -150,7 +151,7 @@ func lookupImageExtractor(kind string, configs kyvernov1.ImageExtractorConfigs) return registeredExtractors[kind] } -func ExtractImagesFromResource(resource unstructured.Unstructured, configs kyvernov1.ImageExtractorConfigs) (map[string]map[string]ImageInfo, error) { +func ExtractImagesFromResource(resource unstructured.Unstructured, configs kyvernov1.ImageExtractorConfigs, cfg config.Configuration) (map[string]map[string]ImageInfo, error) { infos := map[string]map[string]ImageInfo{} extractors := lookupImageExtractor(resource.GetKind(), configs) @@ -159,7 +160,7 @@ func ExtractImagesFromResource(resource unstructured.Unstructured, configs kyver } for _, extractor := range extractors { - if infoMap, err := extractor.ExtractFromResource(resource.Object); err != nil { + if infoMap, err := extractor.ExtractFromResource(resource.Object, cfg); err != nil { return nil, err } else if len(infoMap) > 0 { infos[extractor.Name] = infoMap diff --git a/pkg/utils/api/image_test.go b/pkg/utils/api/image_test.go index a104849b7c..a5fa9f1446 100644 --- a/pkg/utils/api/image_test.go +++ b/pkg/utils/api/image_test.go @@ -4,11 +4,14 @@ import ( "testing" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine/utils" imageutils "github.com/kyverno/kyverno/pkg/utils/image" "gotest.tools/assert" ) +var cfg = config.NewDefaultConfiguration() + func Test_extractImageInfo(t *testing.T) { tests := []struct { extractionConfig kyvernov1.ImageExtractorConfigs @@ -219,7 +222,7 @@ func Test_extractImageInfo(t *testing.T) { for _, test := range tests { resource, err := utils.ConvertToUnstructured(test.raw) assert.NilError(t, err) - images, err := ExtractImagesFromResource(*resource, test.extractionConfig) + images, err := ExtractImagesFromResource(*resource, test.extractionConfig, cfg) assert.NilError(t, err) assert.DeepEqual(t, test.images, images) } diff --git a/pkg/utils/image/infos.go b/pkg/utils/image/infos.go index bf1737fbeb..a670c0d72c 100644 --- a/pkg/utils/image/infos.go +++ b/pkg/utils/image/infos.go @@ -1,12 +1,17 @@ package image import ( + "fmt" "strings" "github.com/distribution/distribution/reference" + "github.com/kyverno/kyverno/pkg/config" "github.com/pkg/errors" + "sigs.k8s.io/controller-runtime/pkg/log" ) +var logger = log.Log.WithName("image") + type ImageInfo struct { // Registry is the URL address of the image registry e.g. `docker.io` Registry string `json:"registry,omitempty"` @@ -25,40 +30,73 @@ type ImageInfo struct { } func (i *ImageInfo) String() string { - image := i.Registry + "/" + i.Path - if i.Digest != "" { - return image + "@" + i.Digest + var image string + if i.Registry != "" { + image = fmt.Sprintf("%s/%s", i.Registry, i.Path) } else { - return image + ":" + i.Tag + image = i.Path + } + if i.Digest != "" { + return fmt.Sprintf("%s@%s", image, i.Digest) + } else { + return fmt.Sprintf("%s:%s", image, i.Tag) } } func (i *ImageInfo) ReferenceWithTag() string { - return i.Registry + "/" + i.Path + ":" + i.Tag + if i.Registry != "" { + return fmt.Sprintf("%s/%s:%s", i.Registry, i.Path, i.Tag) + } else { + return fmt.Sprintf("%s:%s", i.Path, i.Tag) + } } -func GetImageInfo(image string) (*ImageInfo, error) { - image = addDefaultDomain(image) - ref, err := reference.Parse(image) +func GetImageInfo(image string, cfg config.Configuration) (*ImageInfo, error) { + logger.V(2).Info( + "Getting the image info", + image, "image", + "defaultRegistry", config.Configuration.GetDefaultRegistry(cfg), + "enableDefaultRegistryMutation", config.Configuration.GetEnableDefaultRegistryMutation(cfg), + ) + // adding the default domain in order to properly parse image info + fullImageName := addDefaultRegistry(image, cfg) + ref, err := reference.Parse(fullImageName) if err != nil { - return nil, errors.Wrapf(err, "bad image: %s", image) + return nil, errors.Wrapf(err, "bad image: %s", fullImageName) } + var registry, path, name, tag, digest string if named, ok := ref.(reference.Named); ok { registry = reference.Domain(named) path = reference.Path(named) name = path[strings.LastIndex(path, "/")+1:] } + if tagged, ok := ref.(reference.Tagged); ok { tag = tagged.Tag() } if digested, ok := ref.(reference.Digested); ok { digest = digested.Digest().String() } - // set default tag - the domain is set via addDefaultDomain before parsing + // set default tag - the domain is set via addDefaultRegistry before parsing if digest == "" && tag == "" { tag = "latest" } + // if registry mutation isn't enabled don't add the default registry + if fullImageName != image && !config.Configuration.GetEnableDefaultRegistryMutation(cfg) { + registry = "" + } + + logger.V(2).Info( + "Getting the image info", + "image", image, + "registry", registry, + "name", name, + "path", path, + "tag", tag, + "digest", digest, + ) + return &ImageInfo{ Registry: registry, Name: name, @@ -68,10 +106,11 @@ func GetImageInfo(image string) (*ImageInfo, error) { }, nil } -func addDefaultDomain(name string) string { +// addDefaultRegistry always adds default registry +func addDefaultRegistry(name string, cfg config.Configuration) string { i := strings.IndexRune(name, '/') if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost" && strings.ToLower(name[:i]) == name[:i]) { - return "docker.io/" + name + name = fmt.Sprintf("%s/%s", config.Configuration.GetDefaultRegistry(cfg), name) } return name } diff --git a/pkg/utils/image/infos_test.go b/pkg/utils/image/infos_test.go index f94e32dfc3..68910c529d 100644 --- a/pkg/utils/image/infos_test.go +++ b/pkg/utils/image/infos_test.go @@ -1,11 +1,33 @@ package image import ( + "strconv" "testing" + "github.com/kyverno/kyverno/pkg/config" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" ) +// initializeMockConfig initializes a basic configuration with a fake dynamic client +func initializeMockConfig(defaultRegistry string, enableDefaultRegistryMutation bool) (config.Configuration, error) { + configMapData := make(map[string]string, 0) + configMapData["defaultRegistry"] = defaultRegistry + configMapData["enableDefaultRegistryMutation"] = strconv.FormatBool(enableDefaultRegistryMutation) + cm := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Namespace: "kyverno", Name: "kyverno"}, + Data: configMapData, + } + cs := fake.NewSimpleClientset(&cm) + dynamicConfig, err := config.NewConfiguration(cs) + if err != nil { + return nil, err + } + return dynamicConfig, nil +} + func Test_GetImageInfo(t *testing.T) { validateImageInfo(t, "nginx", @@ -14,7 +36,9 @@ func Test_GetImageInfo(t *testing.T) { "docker.io", "latest", "", - "docker.io/nginx:latest") + "docker.io/nginx:latest", + "docker.io", + true) validateImageInfo(t, "nginx:v10.3", @@ -23,7 +47,9 @@ func Test_GetImageInfo(t *testing.T) { "docker.io", "v10.3", "", - "docker.io/nginx:v10.3") + "docker.io/nginx:v10.3", + "docker.io", + true) validateImageInfo(t, "docker.io/test/nginx:v10.3", @@ -32,7 +58,9 @@ func Test_GetImageInfo(t *testing.T) { "docker.io", "v10.3", "", - "docker.io/test/nginx:v10.3") + "docker.io/test/nginx:v10.3", + "docker.io", + true) validateImageInfo(t, "test/nginx", @@ -41,7 +69,9 @@ func Test_GetImageInfo(t *testing.T) { "docker.io", "latest", "", - "docker.io/test/nginx:latest") + "docker.io/test/nginx:latest", + "docker.io", + true) validateImageInfo(t, "localhost:4443/test/nginx", @@ -50,7 +80,10 @@ func Test_GetImageInfo(t *testing.T) { "localhost:4443", "latest", "", - "localhost:4443/test/nginx:latest") + "localhost:4443/test/nginx:latest", + "docker.io", + true) + validateImageInfo(t, "docker.io/test/centos@sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839686a31da35a3b532f", "centos", @@ -58,7 +91,31 @@ func Test_GetImageInfo(t *testing.T) { "docker.io", "", "sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839686a31da35a3b532f", - "docker.io/test/centos@sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839686a31da35a3b532f") + "docker.io/test/centos@sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839686a31da35a3b532f", + "docker.io", + true) + + validateImageInfo(t, + "test/nginx", + "nginx", + "test/nginx", + "gcr.io", + "latest", + "", + "gcr.io/test/nginx:latest", + "gcr.io", + true) + + validateImageInfo(t, + "test/nginx", + "nginx", + "test/nginx", + "", + "latest", + "", + "test/nginx:latest", + "gcr.io", + false) } func Test_ReferenceWithTag(t *testing.T) { @@ -84,8 +141,10 @@ func Test_ReferenceWithTag(t *testing.T) { input: "docker.io/test/centos@sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839686a31da35a3b532f", expected: "docker.io/test/centos:", }} + cfg, err := initializeMockConfig("docker.io", true) + assert.NoError(t, err) for _, test := range testCases { - imageInfo, err := GetImageInfo(test.input) + imageInfo, err := GetImageInfo(test.input, cfg) assert.NoError(t, err) assert.Equal(t, test.expected, imageInfo.ReferenceWithTag()) } @@ -95,15 +154,20 @@ func Test_ParseError(t *testing.T) { testCases := []string{ "++", } + cfg, err := initializeMockConfig("docker.io", true) + assert.NoError(t, err) for _, test := range testCases { - imageInfo, err := GetImageInfo(test) + + imageInfo, err := GetImageInfo(test, cfg) assert.Error(t, err) assert.Nil(t, imageInfo) } } -func validateImageInfo(t *testing.T, raw, name, path, registry, tag, digest, str string) { - i1, err := GetImageInfo(raw) +func validateImageInfo(t *testing.T, raw, name, path, registry, tag, digest, str string, defautRegistry string, enableDefaultRegistryMutation bool) { + cfg, err := initializeMockConfig(defautRegistry, enableDefaultRegistryMutation) + assert.NoError(t, err) + i1, err := GetImageInfo(raw, cfg) assert.NoError(t, err) assert.Equal(t, name, i1.Name) assert.Equal(t, path, i1.Path) @@ -112,3 +176,56 @@ func validateImageInfo(t *testing.T, raw, name, path, registry, tag, digest, str assert.Equal(t, digest, i1.Digest) assert.Equal(t, str, i1.String()) } + +func Test_addDefaultRegistry(t *testing.T) { + tests := []struct { + input string + defaultRegistry string + enableDefaultRegistryMutation bool + want string + }{ + { + defaultRegistry: "test.io", + enableDefaultRegistryMutation: true, + input: "docker.io/test/nginx:v10.4", + want: "docker.io/test/nginx:v10.4", + }, + { + defaultRegistry: "docker.io", + enableDefaultRegistryMutation: true, + input: "test/nginx:v10.3", + want: "docker.io/test/nginx:v10.3", + }, + { + defaultRegistry: "myregistry.io", + enableDefaultRegistryMutation: false, + input: "test/nginx:v10.6", + want: "myregistry.io/test/nginx:v10.6", + }, + { + input: "localhost/netd:v0.4.4-gke.0", + defaultRegistry: "docker.io", + enableDefaultRegistryMutation: true, + want: "localhost/netd:v0.4.4-gke.0", + }, + { + input: "myregistry.org/test/nginx:v10.3", + defaultRegistry: "docker.io", + enableDefaultRegistryMutation: false, + want: "myregistry.org/test/nginx:v10.3", + }, + { + input: "test/centos@sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839686a31da35a3b532f", + defaultRegistry: "docker.io", + enableDefaultRegistryMutation: true, + want: "docker.io/test/centos@sha256:dead07b4d8ed7e29e98de0f4504d87e8880d4347859d839686a31da35a3b532f", + }, + } + + for _, tt := range tests { + cfg, err := initializeMockConfig(tt.defaultRegistry, true) + assert.NoError(t, err) + got := addDefaultRegistry(tt.input, cfg) + assert.Equal(t, tt.want, got) + } +} diff --git a/pkg/webhooks/resource/handlers.go b/pkg/webhooks/resource/handlers.go index f00f28bbdc..22a893bd53 100644 --- a/pkg/webhooks/resource/handlers.go +++ b/pkg/webhooks/resource/handlers.go @@ -137,7 +137,7 @@ func (h *handlers) Validate(ctx context.Context, logger logr.Logger, request *ad namespaceLabels = engineutils.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, h.nsLister, logger) } - vh := validation.NewValidationHandler(logger, h.kyvernoClient, h.rclient, h.pCache, h.pcBuilder, h.eventGen, h.admissionReports, h.metricsConfig) + vh := validation.NewValidationHandler(logger, h.kyvernoClient, h.rclient, h.pCache, h.pcBuilder, h.eventGen, h.admissionReports, h.metricsConfig, h.configuration) ok, msg, warnings := vh.HandleValidation(ctx, request, policies, policyContext, namespaceLabels, startTime) if !ok { @@ -184,7 +184,7 @@ func (h *handlers) Mutate(ctx context.Context, logger logr.Logger, request *admi logger.Error(err, "failed to build policy context") return admissionutils.Response(request.UID, err) } - ivh := imageverification.NewImageVerificationHandler(logger, h.kyvernoClient, h.rclient, h.eventGen, h.admissionReports) + ivh := imageverification.NewImageVerificationHandler(logger, h.kyvernoClient, h.rclient, h.eventGen, h.admissionReports, h.configuration) imagePatches, imageVerifyWarnings, err := ivh.Handle(ctx, newRequest, verifyImagesPolicies, policyContext) if err != nil { logger.Error(err, "image verification failed") diff --git a/pkg/webhooks/resource/imageverification/handler.go b/pkg/webhooks/resource/imageverification/handler.go index 1856b4e3ee..da871d2586 100644 --- a/pkg/webhooks/resource/imageverification/handler.go +++ b/pkg/webhooks/resource/imageverification/handler.go @@ -9,6 +9,7 @@ import ( "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/client/clientset/versioned" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/event" @@ -35,6 +36,7 @@ type imageVerificationHandler struct { log logr.Logger eventGen event.Interface admissionReports bool + cfg config.Configuration } func NewImageVerificationHandler( @@ -43,6 +45,7 @@ func NewImageVerificationHandler( rclient registryclient.Client, eventGen event.Interface, admissionReports bool, + cfg config.Configuration, ) ImageVerificationHandler { return &imageVerificationHandler{ kyvernoClient: kyvernoClient, @@ -50,6 +53,7 @@ func NewImageVerificationHandler( log: log, eventGen: eventGen, admissionReports: admissionReports, + cfg: cfg, } } @@ -87,7 +91,7 @@ func (h *imageVerificationHandler) handleVerifyImages( fmt.Sprintf("POLICY %s/%s", policy.GetNamespace(), policy.GetName()), func(ctx context.Context, span trace.Span) { policyContext := policyContext.WithPolicy(policy) - resp, ivm := engine.VerifyAndPatchImages(ctx, h.rclient, policyContext) + resp, ivm := engine.VerifyAndPatchImages(ctx, h.rclient, policyContext, h.cfg) engineResponses = append(engineResponses, resp) patches = append(patches, resp.GetPatches()...) diff --git a/pkg/webhooks/resource/validation/validation.go b/pkg/webhooks/resource/validation/validation.go index 7a9275f9f5..741e8bca46 100644 --- a/pkg/webhooks/resource/validation/validation.go +++ b/pkg/webhooks/resource/validation/validation.go @@ -9,6 +9,7 @@ import ( "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" "github.com/kyverno/kyverno/pkg/client/clientset/versioned" + "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine" "github.com/kyverno/kyverno/pkg/engine/response" "github.com/kyverno/kyverno/pkg/event" @@ -43,6 +44,7 @@ func NewValidationHandler( eventGen event.Interface, admissionReports bool, metrics metrics.MetricsConfigManager, + cfg config.Configuration, ) ValidationHandler { return &validationHandler{ log: log, @@ -53,6 +55,7 @@ func NewValidationHandler( eventGen: eventGen, admissionReports: admissionReports, metrics: metrics, + cfg: cfg, } } @@ -65,6 +68,7 @@ type validationHandler struct { eventGen event.Interface admissionReports bool metrics metrics.MetricsConfigManager + cfg config.Configuration } func (v *validationHandler) HandleValidation( @@ -110,7 +114,7 @@ func (v *validationHandler) HandleValidation( failurePolicy = kyvernov1.Fail } - engineResponse := engine.Validate(ctx, v.rclient, policyContext) + engineResponse := engine.Validate(ctx, v.rclient, policyContext, v.cfg) if engineResponse.IsNil() { // we get an empty response if old and new resources created the same response // allow updates if resource update doesnt change the policy evaluation @@ -169,7 +173,7 @@ func (v *validationHandler) buildAuditResponses( fmt.Sprintf("POLICY %s/%s", policy.GetNamespace(), policy.GetName()), func(ctx context.Context, span trace.Span) { policyContext := policyContext.WithPolicy(policy).WithNamespaceLabels(namespaceLabels) - responses = append(responses, engine.Validate(ctx, v.rclient, policyContext)) + responses = append(responses, engine.Validate(ctx, v.rclient, policyContext, v.cfg)) }, ) } diff --git a/pkg/webhooks/resource/validation_test.go b/pkg/webhooks/resource/validation_test.go index feb59d6127..659acd9183 100644 --- a/pkg/webhooks/resource/validation_test.go +++ b/pkg/webhooks/resource/validation_test.go @@ -6,6 +6,7 @@ import ( "fmt" "testing" + "github.com/kyverno/kyverno/pkg/config" log "github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/registryclient" @@ -34,7 +35,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { }, "spec": { "validationFailureAction": "audit", - "validationFailureActionOverrides": + "validationFailureActionOverrides": [ { "action": "enforce", @@ -104,7 +105,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { }, "spec": { "validationFailureAction": "audit", - "validationFailureActionOverrides": + "validationFailureActionOverrides": [ { "action": "enforce", @@ -176,7 +177,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { }, "spec": { "validationFailureAction": "audit", - "validationFailureActionOverrides": + "validationFailureActionOverrides": [ { "action": "enforce", @@ -246,7 +247,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { }, "spec": { "validationFailureAction": "enforce", - "validationFailureActionOverrides": + "validationFailureActionOverrides": [ { "action": "enforce", @@ -316,7 +317,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { }, "spec": { "validationFailureAction": "enforce", - "validationFailureActionOverrides": + "validationFailureActionOverrides": [ { "action": "enforce", @@ -388,7 +389,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { }, "spec": { "validationFailureAction": "enforce", - "validationFailureActionOverrides": + "validationFailureActionOverrides": [ { "action": "enforce", @@ -458,7 +459,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { }, "spec": { "validationFailureAction": "enforce", - "validationFailureActionOverrides": + "validationFailureActionOverrides": [ { "action": "enforce", @@ -523,6 +524,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { }, } + cfg := config.NewDefaultConfiguration() for i, tc := range testcases { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { var policy kyvernov1.ClusterPolicy @@ -535,6 +537,7 @@ func TestValidate_failure_action_overrides(t *testing.T) { context.TODO(), registryclient.NewOrDie(), engine.NewPolicyContext().WithPolicy(&policy).WithNewResource(*resourceUnstructured), + cfg, ) if tc.blocked && tc.messages != nil { for _, r := range er.PolicyResponse.Rules { @@ -563,7 +566,7 @@ func Test_RuleSelector(t *testing.T) { "match": {"name": "test-*", "resources": {"kinds": ["Pod"]}}, "validate": { "message": "The label 'app' is required.", - "pattern": { "metadata": { "labels": { "app": "?*" } } } + "pattern": { "metadata": { "labels": { "app": "?*" } } } } }, { @@ -571,7 +574,7 @@ func Test_RuleSelector(t *testing.T) { "match": {"name": "*", "resources": {"kinds": ["Pod"]}}, "validate": { "message": "The label 'app' is required.", - "pattern": { "metadata": { "labels": { "app": "?*", "test" : "?*" } } } + "pattern": { "metadata": { "labels": { "app": "?*", "test" : "?*" } } } } } ] @@ -595,7 +598,8 @@ func Test_RuleSelector(t *testing.T) { ctx := engine.NewPolicyContext().WithPolicy(&policy).WithNewResource(*resourceUnstructured) - resp := engine.Validate(context.TODO(), registryclient.NewOrDie(), ctx) + cfg := config.NewDefaultConfiguration() + resp := engine.Validate(context.TODO(), registryclient.NewOrDie(), ctx, cfg) assert.Assert(t, resp.PolicyResponse.RulesAppliedCount == 2) assert.Assert(t, resp.PolicyResponse.RulesErrorCount == 0) @@ -606,7 +610,7 @@ func Test_RuleSelector(t *testing.T) { applyOne := kyvernov1.ApplyOne policy.Spec.ApplyRules = &applyOne - resp = engine.Validate(context.TODO(), registryclient.NewOrDie(), ctx) + resp = engine.Validate(context.TODO(), registryclient.NewOrDie(), ctx, cfg) assert.Assert(t, resp.PolicyResponse.RulesAppliedCount == 1) assert.Assert(t, resp.PolicyResponse.RulesErrorCount == 0)