diff --git a/charts/kyverno/README.md b/charts/kyverno/README.md index fc8b7ff92a..31564eae6d 100644 --- a/charts/kyverno/README.md +++ b/charts/kyverno/README.md @@ -273,6 +273,7 @@ The chart values are organised per component. | 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.webhooks | list | `[]` | Defines the `namespaceSelector` in the webhook configurations. Note that it takes a list of `namespaceSelector` and/or `objectSelector` in the JSON format, and only the first element will be forwarded to the webhook configurations. The Kyverno namespace is excluded if `excludeKyvernoNamespace` is `true` (default) | | config.webhookAnnotations | object | `{}` | Defines annotations to set on webhook configurations. | +| config.webhookLabels | object | `{}` | Defines labels to set on webhook configurations. | | config.matchConditions | list | `[]` | Defines match conditions to set on webhook configurations (requires Kubernetes 1.27+). | | config.excludeKyvernoNamespace | bool | `true` | Exclude Kyverno namespace Determines if default Kyverno namespace exclusion is enabled for webhooks and resourceFilters | | config.resourceFiltersExcludeNamespaces | list | `[]` | resourceFilter namespace exclude Namespaces to exclude from the default resourceFilters | diff --git a/charts/kyverno/templates/config/configmap.yaml b/charts/kyverno/templates/config/configmap.yaml index 7a0370619a..16c1272c4b 100644 --- a/charts/kyverno/templates/config/configmap.yaml +++ b/charts/kyverno/templates/config/configmap.yaml @@ -42,6 +42,9 @@ data: {{- with .Values.config.webhookAnnotations }} webhookAnnotations: {{ toJson . | quote }} {{- end }} + {{- with .Values.config.webhookLabels }} + webhookLabels: {{ toJson . | quote }} + {{- end }} {{- with .Values.config.matchConditions }} matchConditions: {{ toJson . | quote }} {{- end }} diff --git a/charts/kyverno/values.yaml b/charts/kyverno/values.yaml index ecc4d0163d..7666d3bf3f 100644 --- a/charts/kyverno/values.yaml +++ b/charts/kyverno/values.yaml @@ -232,6 +232,11 @@ config: # Example to disable admission enforcer on AKS: # 'admissions.enforcer/disabled': 'true' + # -- Defines labels to set on webhook configurations. + webhookLabels: {} + # Example to adopt webhook resources in ArgoCD: + # 'argocd.argoproj.io/instance': 'kyverno' + # -- Defines match conditions to set on webhook configurations (requires Kubernetes 1.27+). matchConditions: [] diff --git a/pkg/config/config.go b/pkg/config/config.go index e423efab14..5afa3ddcfa 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -88,6 +88,7 @@ const ( generateSuccessEvents = "generateSuccessEvents" webhooks = "webhooks" webhookAnnotations = "webhookAnnotations" + webhookLabels = "webhookLabels" matchConditions = "matchConditions" ) @@ -162,6 +163,8 @@ type Configuration interface { GetWebhooks() []WebhookConfig // GetWebhookAnnotations returns annotations to set on webhook configs GetWebhookAnnotations() map[string]string + // GetWebhookLabels returns labels to set on webhook configs + GetWebhookLabels() map[string]string // GetMatchConditions returns match conditions to set on webhook configs GetMatchConditions() []admissionregistrationv1.MatchCondition // Load loads configuration from a configmap @@ -181,6 +184,7 @@ type configuration struct { generateSuccessEvents bool webhooks []WebhookConfig webhookAnnotations map[string]string + webhookLabels map[string]string matchConditions []admissionregistrationv1.MatchCondition mux sync.RWMutex callbacks []func() @@ -300,6 +304,12 @@ func (cd *configuration) GetWebhookAnnotations() map[string]string { return cd.webhookAnnotations } +func (cd *configuration) GetWebhookLabels() map[string]string { + cd.mux.RLock() + defer cd.mux.RUnlock() + return cd.webhookLabels +} + func (cd *configuration) GetMatchConditions() []admissionregistrationv1.MatchCondition { cd.mux.RLock() defer cd.mux.RUnlock() @@ -332,6 +342,7 @@ func (cd *configuration) load(cm *corev1.ConfigMap) { cd.generateSuccessEvents = false cd.webhooks = nil cd.webhookAnnotations = nil + cd.webhookLabels = nil cd.matchConditions = nil // load filters cd.filters = parseKinds(data[resourceFilters]) @@ -437,6 +448,20 @@ func (cd *configuration) load(cm *corev1.ConfigMap) { logger.Info("webhookAnnotations configured") } } + // load webhook annotations + webhookLabels, ok := data[webhookLabels] + if !ok { + logger.Info("webhookLabels not set") + } else { + logger := logger.WithValues("webhookLabels", webhookLabels) + webhookLabels, err := parseWebhookLabels(webhookLabels) + if err != nil { + logger.Error(err, "failed to parse webhook labels") + } else { + cd.webhookLabels = webhookLabels + logger.Info("webhookLabels configured") + } + } // load match conditions matchConditions, ok := data[matchConditions] if !ok { @@ -465,6 +490,7 @@ func (cd *configuration) unload() { cd.generateSuccessEvents = false cd.webhooks = nil cd.webhookAnnotations = nil + cd.webhookLabels = nil logger.Info("configuration unloaded") } diff --git a/pkg/config/types.go b/pkg/config/types.go index 618bae25c8..c41d036b50 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -55,6 +55,14 @@ func parseWebhookAnnotations(in string) (map[string]string, error) { return out, nil } +func parseWebhookLabels(in string) (map[string]string, error) { + var out map[string]string + if err := json.Unmarshal([]byte(in), &out); err != nil { + return nil, err + } + return out, nil +} + func parseMatchConditions(in string) ([]admissionregistrationv1.MatchCondition, error) { var out []admissionregistrationv1.MatchCondition if err := json.Unmarshal([]byte(in), &out); err != nil { diff --git a/pkg/config/types_test.go b/pkg/config/types_test.go index 19ada30cba..ba9e9c278c 100644 --- a/pkg/config/types_test.go +++ b/pkg/config/types_test.go @@ -258,6 +258,43 @@ func Test_parseWebhookAnnotations(t *testing.T) { } } +func Test_parseWebhookLabels(t *testing.T) { + type args struct { + in string + } + tests := []struct { + name string + args args + want map[string]string + wantErr bool + }{{ + args: args{"hello"}, + wantErr: true, + }, { + args: args{""}, + wantErr: true, + }, { + args: args{"null"}, + }, { + args: args{`{"a": "b"}`}, + want: map[string]string{ + "a": "b", + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseWebhookLabels(tt.args.in) + if (err != nil) != tt.wantErr { + t.Errorf("parseWebhookLabels() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseWebhookLabels() = %v, want %v", got, tt.want) + } + }) + } +} + func Test_parseBucketBoundariesConfig(t *testing.T) { var emptyBoundaries []float64 diff --git a/pkg/controllers/generic/webhook/controller.go b/pkg/controllers/generic/webhook/controller.go index 3f01170e3e..f3cdc1778a 100644 --- a/pkg/controllers/generic/webhook/controller.go +++ b/pkg/controllers/generic/webhook/controller.go @@ -12,6 +12,7 @@ import ( "github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/tls" controllerutils "github.com/kyverno/kyverno/pkg/utils/controller" + "golang.org/x/exp/maps" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -164,12 +165,16 @@ func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, _, return err } -func objectMeta(name string, annotations map[string]string, owner ...metav1.OwnerReference) metav1.ObjectMeta { +func objectMeta(name string, annotations map[string]string, labels map[string]string, owner ...metav1.OwnerReference) metav1.ObjectMeta { + desiredLabels := make(map[string]string) + defaultLabels := map[string]string{ + kyverno.LabelWebhookManagedBy: kyverno.ValueKyvernoApp, + } + maps.Copy(desiredLabels, labels) + maps.Copy(desiredLabels, defaultLabels) return metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{ - kyverno.LabelWebhookManagedBy: kyverno.ValueKyvernoApp, - }, + Name: name, + Labels: desiredLabels, Annotations: annotations, OwnerReferences: owner, } @@ -177,7 +182,7 @@ func objectMeta(name string, annotations map[string]string, owner ...metav1.Owne func (c *controller) build(cfg config.Configuration, caBundle []byte) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) { return &admissionregistrationv1.ValidatingWebhookConfiguration{ - ObjectMeta: objectMeta(c.webhookName, cfg.GetWebhookAnnotations()), + ObjectMeta: objectMeta(c.webhookName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels()), Webhooks: []admissionregistrationv1.ValidatingWebhook{{ Name: fmt.Sprintf("%s.%s.svc", config.KyvernoServiceName(), config.KyvernoNamespace()), ClientConfig: c.clientConfig(caBundle), diff --git a/pkg/controllers/webhook/controller.go b/pkg/controllers/webhook/controller.go index 465058b323..f6bfcd4f4e 100644 --- a/pkg/controllers/webhook/controller.go +++ b/pkg/controllers/webhook/controller.go @@ -512,7 +512,7 @@ func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, nam func (c *controller) buildVerifyMutatingWebhookConfiguration(_ context.Context, cfg config.Configuration, caBundle []byte) (*admissionregistrationv1.MutatingWebhookConfiguration, error) { return &admissionregistrationv1.MutatingWebhookConfiguration{ - ObjectMeta: objectMeta(config.VerifyMutatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), c.buildOwner()...), + ObjectMeta: objectMeta(config.VerifyMutatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels(), c.buildOwner()...), Webhooks: []admissionregistrationv1.MutatingWebhook{{ Name: config.VerifyMutatingWebhookName, ClientConfig: c.clientConfig(caBundle, config.VerifyMutatingWebhookServicePath), @@ -539,7 +539,7 @@ func (c *controller) buildVerifyMutatingWebhookConfiguration(_ context.Context, func (c *controller) buildPolicyMutatingWebhookConfiguration(_ context.Context, cfg config.Configuration, caBundle []byte) (*admissionregistrationv1.MutatingWebhookConfiguration, error) { return &admissionregistrationv1.MutatingWebhookConfiguration{ - ObjectMeta: objectMeta(config.PolicyMutatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), c.buildOwner()...), + ObjectMeta: objectMeta(config.PolicyMutatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels(), c.buildOwner()...), Webhooks: []admissionregistrationv1.MutatingWebhook{{ Name: config.PolicyMutatingWebhookName, ClientConfig: c.clientConfig(caBundle, config.PolicyMutatingWebhookServicePath), @@ -562,7 +562,7 @@ func (c *controller) buildPolicyMutatingWebhookConfiguration(_ context.Context, func (c *controller) buildPolicyValidatingWebhookConfiguration(_ context.Context, cfg config.Configuration, caBundle []byte) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) { return &admissionregistrationv1.ValidatingWebhookConfiguration{ - ObjectMeta: objectMeta(config.PolicyValidatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), c.buildOwner()...), + ObjectMeta: objectMeta(config.PolicyValidatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels(), c.buildOwner()...), Webhooks: []admissionregistrationv1.ValidatingWebhook{{ Name: config.PolicyValidatingWebhookName, ClientConfig: c.clientConfig(caBundle, config.PolicyValidatingWebhookServicePath), @@ -584,7 +584,7 @@ func (c *controller) buildPolicyValidatingWebhookConfiguration(_ context.Context func (c *controller) buildDefaultResourceMutatingWebhookConfiguration(_ context.Context, cfg config.Configuration, caBundle []byte) (*admissionregistrationv1.MutatingWebhookConfiguration, error) { return &admissionregistrationv1.MutatingWebhookConfiguration{ - ObjectMeta: objectMeta(config.MutatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), c.buildOwner()...), + ObjectMeta: objectMeta(config.MutatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels(), c.buildOwner()...), Webhooks: []admissionregistrationv1.MutatingWebhook{{ Name: config.MutatingWebhookName + "-ignore", ClientConfig: c.clientConfig(caBundle, config.MutatingWebhookServicePath+"/ignore"), @@ -630,7 +630,7 @@ func (c *controller) buildDefaultResourceMutatingWebhookConfiguration(_ context. func (c *controller) buildResourceMutatingWebhookConfiguration(ctx context.Context, cfg config.Configuration, caBundle []byte) (*admissionregistrationv1.MutatingWebhookConfiguration, error) { result := admissionregistrationv1.MutatingWebhookConfiguration{ - ObjectMeta: objectMeta(config.MutatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), c.buildOwner()...), + ObjectMeta: objectMeta(config.MutatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels(), c.buildOwner()...), Webhooks: []admissionregistrationv1.MutatingWebhook{}, } if c.watchdogCheck() { @@ -708,7 +708,7 @@ func (c *controller) buildDefaultResourceValidatingWebhookConfiguration(_ contex sideEffects = &noneOnDryRun } return &admissionregistrationv1.ValidatingWebhookConfiguration{ - ObjectMeta: objectMeta(config.ValidatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), c.buildOwner()...), + ObjectMeta: objectMeta(config.ValidatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels(), c.buildOwner()...), Webhooks: []admissionregistrationv1.ValidatingWebhook{{ Name: config.ValidatingWebhookName + "-ignore", ClientConfig: c.clientConfig(caBundle, config.ValidatingWebhookServicePath+"/ignore"), @@ -756,7 +756,7 @@ func (c *controller) buildDefaultResourceValidatingWebhookConfiguration(_ contex func (c *controller) buildResourceValidatingWebhookConfiguration(ctx context.Context, cfg config.Configuration, caBundle []byte) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) { result := admissionregistrationv1.ValidatingWebhookConfiguration{ - ObjectMeta: objectMeta(config.ValidatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), c.buildOwner()...), + ObjectMeta: objectMeta(config.ValidatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels(), c.buildOwner()...), Webhooks: []admissionregistrationv1.ValidatingWebhook{}, } if c.watchdogCheck() { diff --git a/pkg/controllers/webhook/utils.go b/pkg/controllers/webhook/utils.go index c455808105..b93ba3954e 100644 --- a/pkg/controllers/webhook/utils.go +++ b/pkg/controllers/webhook/utils.go @@ -7,6 +7,7 @@ import ( "github.com/kyverno/kyverno/api/kyverno" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" + "golang.org/x/exp/maps" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -85,12 +86,16 @@ func (wh *webhook) isEmpty() bool { return len(wh.rules) == 0 } -func objectMeta(name string, annotations map[string]string, owner ...metav1.OwnerReference) metav1.ObjectMeta { +func objectMeta(name string, annotations map[string]string, labels map[string]string, owner ...metav1.OwnerReference) metav1.ObjectMeta { + desiredLabels := make(map[string]string) + defaultLabels := map[string]string{ + kyverno.LabelWebhookManagedBy: kyverno.ValueKyvernoApp, + } + maps.Copy(desiredLabels, labels) + maps.Copy(desiredLabels, defaultLabels) return metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{ - kyverno.LabelWebhookManagedBy: kyverno.ValueKyvernoApp, - }, + Name: name, + Labels: desiredLabels, Annotations: annotations, OwnerReferences: owner, }