1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

feat: add webhooks object selector support (#3413)

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>

Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2022-03-29 17:09:44 +02:00 committed by GitHub
parent bdb675b9c0
commit b4cf89e57f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 53 deletions

View file

@ -109,7 +109,7 @@ The command removes all the Kubernetes components associated with the chart and
| config.existingConfig | string | `""` | Name of an existing config map (ignores default/provided resourceFilters) |
| config.excludeGroupRole | string | `nil` | Exclude group role |
| config.excludeUsername | string | `nil` | Exclude username |
| config.webhooks | string | `nil` | Defines the `namespaceSelector` in the webhook configurations. Note that it takes a list of `namespaceSelector` in the JSON format, and only the first element will be forwarded to the webhook configurations. |
| config.webhooks | string | `nil` | 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. |
| config.generateSuccessEvents | bool | `false` | Generate success events. |
| config.metricsConfig | object | `{"namespaces":{"exclude":[],"include":[]}}` | Metrics config. |
| updateStrategy | object | See [values.yaml](values.yaml) | Deployment update strategy. Ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy |

View file

@ -247,9 +247,22 @@ config:
excludeUsername:
# - ''
# -- Defines the `namespaceSelector` in the webhook configurations.
# Note that it takes a list of `namespaceSelector` in the JSON format, and only the first element
# 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.
webhooks: # [{"namespaceSelector":{"matchExpressions":[{"key":"environment","operator":"In","values":["prod"]}]}}]
webhooks:
# Exlude namespaces
# - namespaceSelector:
# matchExpressions:
# - key: environment
# operator: In
# values:
# - prod
# Exlude objects
# - objectSelector:
# matchExpressions:
# - key: webhooks.kyverno.io/exclude
# operator: DoesNotExist
# -- Generate success events.
generateSuccessEvents: false
# -- Metrics config.

View file

@ -26,6 +26,7 @@ var defaultExcludeGroupRole []string = []string{"system:serviceaccounts:kube-sys
type WebhookConfig struct {
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"`
ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,11,opt,name=objectSelector"`
}
// ConfigData stores the configuration

View file

@ -20,6 +20,7 @@ import (
admregapi "k8s.io/api/admissionregistration/v1"
corev1 "k8s.io/api/core/v1"
errorsapi "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@ -218,30 +219,19 @@ func (wrc *Register) UpdateWebhookConfigurations(configHandler config.Interface)
<-wrc.UpdateWebhookChan
logger.V(4).Info("received the signal to update webhook configurations")
var nsSelector map[string]interface{}
webhookCfgs := configHandler.GetWebhooks()
if webhookCfgs != nil {
selector := webhookCfgs[0].NamespaceSelector
selectorBytes, err := json.Marshal(*selector)
if err != nil {
logger.Error(err, "failed to serialize namespaceSelector")
continue
}
if err = json.Unmarshal(selectorBytes, &nsSelector); err != nil {
logger.Error(err, "failed to convert namespaceSelector to the map")
continue
}
webhookCfg := config.WebhookConfig{}
if len(webhookCfgs) > 0 {
webhookCfg = webhookCfgs[0]
}
retry := false
if err := wrc.updateResourceMutatingWebhookConfiguration(nsSelector); err != nil {
if err := wrc.updateResourceMutatingWebhookConfiguration(webhookCfg); err != nil {
logger.Error(err, "unable to update mutatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
retry = true
}
if err := wrc.updateResourceValidatingWebhookConfiguration(nsSelector); err != nil {
if err := wrc.updateResourceValidatingWebhookConfiguration(webhookCfg); err != nil {
logger.Error(err, "unable to update validatingWebhookConfigurations", "name", getResourceValidatingWebhookConfigName(wrc.serverIP))
retry = true
}
@ -723,7 +713,43 @@ func getHealthyPodsIP(pods []unstructured.Unstructured) (ips []string, errs []er
return
}
func (wrc *Register) updateResourceValidatingWebhookConfiguration(nsSelector map[string]interface{}) error {
func convertLabelSelector(selector *v1.LabelSelector, logger logr.Logger) (map[string]interface{}, error) {
if selector == nil {
return nil, nil
}
if selectorBytes, err := json.Marshal(*selector); err != nil {
logger.Error(err, "failed to serialize selector")
return nil, err
} else {
var nsSelector map[string]interface{}
if err := json.Unmarshal(selectorBytes, &nsSelector); err != nil {
logger.Error(err, "failed to convert namespaceSelector to the map")
return nil, err
}
return nsSelector, nil
}
}
func configureSelector(webhook map[string]interface{}, selector *metav1.LabelSelector, key string, logger logr.Logger) (bool, error) {
currentSelector, _, err := unstructured.NestedMap(webhook, key)
if err != nil {
return false, err
}
if expectedSelector, err := convertLabelSelector(selector, logger); err != nil {
return false, err
} else {
if reflect.DeepEqual(expectedSelector, currentSelector) {
return false, nil
} else {
if err = unstructured.SetNestedMap(webhook, expectedSelector, key); err != nil {
return false, err
}
return true, nil
}
}
}
func (wrc *Register) updateResourceValidatingWebhookConfiguration(webhookCfg config.WebhookConfig) error {
resourceValidatingTyped, err := wrc.vwcLister.Get(getResourceValidatingWebhookConfigName(wrc.serverIP))
if err != nil {
return errors.Wrapf(err, "unable to get validatingWebhookConfigurations")
@ -757,41 +783,37 @@ func (wrc *Register) updateResourceValidatingWebhookConfiguration(nsSelector map
if !ok {
return errors.Wrapf(err, "type mismatched, expected map[string]interface{}, got %T", webhooksUntyped[i])
}
currentSelector, _, err := unstructured.NestedMap(webhook, "namespaceSelector")
if err != nil {
if changed, err := configureSelector(webhook, webhookCfg.ObjectSelector, "objectSelector", wrc.log); err != nil {
return errors.Wrapf(err, "unable to get validatingWebhookConfigurations.webhooks["+fmt.Sprint(i)+"].objectSelector")
} else {
if changed {
webhookChanged = true
}
}
if changed, err := configureSelector(webhook, webhookCfg.NamespaceSelector, "namespaceSelector", wrc.log); err != nil {
return errors.Wrapf(err, "unable to get validatingWebhookConfigurations.webhooks["+fmt.Sprint(i)+"].namespaceSelector")
}
if !reflect.DeepEqual(nsSelector, currentSelector) {
webhookChanged = true
}
if err = unstructured.SetNestedMap(webhook, nsSelector, "namespaceSelector"); err != nil {
return errors.Wrapf(err, "unable to set validatingWebhookConfigurations.webhooks["+fmt.Sprint(i)+"].namespaceSelector")
} else {
if changed {
webhookChanged = true
}
}
webhooks = append(webhooks, webhook)
}
if !webhookChanged {
wrc.log.V(4).Info("namespaceSelector unchanged, skip updating validatingWebhookConfigurations")
return nil
}
if err = unstructured.SetNestedSlice(resourceValidating.UnstructuredContent(), webhooks, "webhooks"); err != nil {
return errors.Wrapf(err, "unable to set validatingWebhookConfigurations.webhooks")
}
if _, err := wrc.client.UpdateResource(resourceValidating.GetAPIVersion(), resourceValidating.GetKind(), "", resourceValidating, false); err != nil {
return err
}
wrc.log.V(3).Info("successfully updated validatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
return nil
}
func (wrc *Register) updateResourceMutatingWebhookConfiguration(nsSelector map[string]interface{}) error {
func (wrc *Register) updateResourceMutatingWebhookConfiguration(webhookCfg config.WebhookConfig) error {
resourceMutatingTyped, err := wrc.mwcLister.Get(getResourceMutatingWebhookConfigName(wrc.serverIP))
if err != nil {
return errors.Wrapf(err, "unable to get mutatingWebhookConfigurations")
@ -825,36 +847,32 @@ func (wrc *Register) updateResourceMutatingWebhookConfiguration(nsSelector map[s
if !ok {
return errors.Wrapf(err, "type mismatched, expected map[string]interface{}, got %T", webhooksUntyped[i])
}
currentSelector, _, err := unstructured.NestedMap(webhook, "namespaceSelector")
if err != nil {
if changed, err := configureSelector(webhook, webhookCfg.ObjectSelector, "objectSelector", wrc.log); err != nil {
return errors.Wrapf(err, "unable to get mutatingWebhookConfigurations.webhooks["+fmt.Sprint(i)+"].objectSelector")
} else {
if changed {
webhookChanged = true
}
}
if changed, err := configureSelector(webhook, webhookCfg.NamespaceSelector, "namespaceSelector", wrc.log); err != nil {
return errors.Wrapf(err, "unable to get mutatingWebhookConfigurations.webhooks["+fmt.Sprint(i)+"].namespaceSelector")
}
if !reflect.DeepEqual(nsSelector, currentSelector) {
webhookChanged = true
}
if err = unstructured.SetNestedMap(webhook, nsSelector, "namespaceSelector"); err != nil {
return errors.Wrapf(err, "unable to set mutatingWebhookConfigurations.webhooks["+fmt.Sprint(i)+"].namespaceSelector")
} else {
if changed {
webhookChanged = true
}
}
webhooks = append(webhooks, webhook)
}
if !webhookChanged {
wrc.log.V(4).Info("namespaceSelector unchanged, skip updating mutatingWebhookConfigurations")
return nil
}
if err = unstructured.SetNestedSlice(resourceMutating.UnstructuredContent(), webhooks, "webhooks"); err != nil {
return errors.Wrapf(err, "unable to set mutatingWebhookConfigurations.webhooks")
}
if _, err := wrc.client.UpdateResource(resourceMutating.GetAPIVersion(), resourceMutating.GetKind(), "", resourceMutating, false); err != nil {
return err
}
wrc.log.V(3).Info("successfully updated mutatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
return nil
}