2022-12-15 09:34:44 +01:00
|
|
|
package webhook
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/go-logr/logr"
|
2023-07-06 10:00:36 +02:00
|
|
|
"github.com/kyverno/kyverno/api/kyverno"
|
2022-12-15 09:34:44 +01:00
|
|
|
"github.com/kyverno/kyverno/pkg/config"
|
|
|
|
"github.com/kyverno/kyverno/pkg/controllers"
|
|
|
|
"github.com/kyverno/kyverno/pkg/logging"
|
|
|
|
"github.com/kyverno/kyverno/pkg/tls"
|
|
|
|
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
|
2024-09-04 16:29:59 +05:30
|
|
|
runtimeutils "github.com/kyverno/kyverno/pkg/utils/runtime"
|
2023-12-07 13:58:31 +01:00
|
|
|
"golang.org/x/exp/maps"
|
2022-12-15 09:34:44 +01:00
|
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
2024-09-04 16:29:59 +05:30
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
2022-12-15 09:34:44 +01:00
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
admissionregistrationv1informers "k8s.io/client-go/informers/admissionregistration/v1"
|
2024-09-04 16:29:59 +05:30
|
|
|
appsv1informers "k8s.io/client-go/informers/apps/v1"
|
2022-12-15 09:34:44 +01:00
|
|
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
|
|
|
admissionregistrationv1listers "k8s.io/client-go/listers/admissionregistration/v1"
|
2024-09-04 16:29:59 +05:30
|
|
|
appsv1listers "k8s.io/client-go/listers/apps/v1"
|
2022-12-15 09:34:44 +01:00
|
|
|
corev1listers "k8s.io/client-go/listers/core/v1"
|
|
|
|
"k8s.io/client-go/util/workqueue"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2023-01-12 12:47:40 +08:00
|
|
|
maxRetries = 10
|
2022-12-15 09:34:44 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2023-08-02 20:17:40 +05:30
|
|
|
none = admissionregistrationv1.SideEffectClassNone
|
|
|
|
fail = admissionregistrationv1.Fail
|
|
|
|
ignore = admissionregistrationv1.Ignore
|
|
|
|
None = &none
|
|
|
|
Fail = &fail
|
|
|
|
Ignore = &ignore
|
2022-12-15 09:34:44 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type controller struct {
|
|
|
|
// clients
|
|
|
|
vwcClient controllerutils.ObjectClient[*admissionregistrationv1.ValidatingWebhookConfiguration]
|
|
|
|
|
|
|
|
// listers
|
2024-09-04 16:29:59 +05:30
|
|
|
vwcLister admissionregistrationv1listers.ValidatingWebhookConfigurationLister
|
|
|
|
secretLister corev1listers.SecretNamespaceLister
|
|
|
|
deploymentLister appsv1listers.DeploymentNamespaceLister
|
2022-12-15 09:34:44 +01:00
|
|
|
|
|
|
|
// queue
|
2024-08-28 19:09:58 +02:00
|
|
|
queue workqueue.TypedRateLimitingInterface[any]
|
2022-12-15 09:34:44 +01:00
|
|
|
|
|
|
|
// config
|
2024-09-04 16:29:59 +05:30
|
|
|
controllerName string
|
|
|
|
logger logr.Logger
|
|
|
|
webhookName string
|
|
|
|
path string
|
|
|
|
server string
|
|
|
|
servicePort int32
|
|
|
|
rules []admissionregistrationv1.RuleWithOperations
|
|
|
|
failurePolicy *admissionregistrationv1.FailurePolicyType
|
|
|
|
sideEffects *admissionregistrationv1.SideEffectClass
|
|
|
|
runtime runtimeutils.Runtime
|
|
|
|
configuration config.Configuration
|
|
|
|
labelSelector *metav1.LabelSelector
|
|
|
|
caSecretName string
|
|
|
|
webhooksDeleted bool
|
|
|
|
autoDeleteWebhooks bool
|
|
|
|
webhookCleanupSetup func(context.Context, logr.Logger) error
|
|
|
|
postWebhookCleanup func(context.Context, logr.Logger) error
|
2022-12-15 09:34:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewController(
|
|
|
|
controllerName string,
|
|
|
|
vwcClient controllerutils.ObjectClient[*admissionregistrationv1.ValidatingWebhookConfiguration],
|
|
|
|
vwcInformer admissionregistrationv1informers.ValidatingWebhookConfigurationInformer,
|
|
|
|
secretInformer corev1informers.SecretInformer,
|
2024-09-04 16:29:59 +05:30
|
|
|
deploymentInformer appsv1informers.DeploymentInformer,
|
2022-12-15 09:34:44 +01:00
|
|
|
webhookName string,
|
|
|
|
path string,
|
|
|
|
server string,
|
2023-01-29 00:54:29 +01:00
|
|
|
servicePort int32,
|
2023-11-17 15:19:53 +01:00
|
|
|
webhookServerPort int32,
|
2023-08-02 20:17:40 +05:30
|
|
|
labelSelector *metav1.LabelSelector,
|
2022-12-15 09:34:44 +01:00
|
|
|
rules []admissionregistrationv1.RuleWithOperations,
|
|
|
|
failurePolicy *admissionregistrationv1.FailurePolicyType,
|
|
|
|
sideEffects *admissionregistrationv1.SideEffectClass,
|
2023-04-06 21:13:32 +02:00
|
|
|
configuration config.Configuration,
|
2023-08-28 16:05:49 +02:00
|
|
|
caSecretName string,
|
2024-09-04 16:29:59 +05:30
|
|
|
runtime runtimeutils.Runtime,
|
|
|
|
autoDeleteWebhooks bool,
|
|
|
|
webhookCleanupSetup func(context.Context, logr.Logger) error,
|
|
|
|
postWebhookCleanup func(context.Context, logr.Logger) error,
|
2022-12-15 09:34:44 +01:00
|
|
|
) controllers.Controller {
|
2024-11-04 21:48:58 +08:00
|
|
|
queue := workqueue.NewTypedRateLimitingQueueWithConfig(
|
|
|
|
workqueue.DefaultTypedControllerRateLimiter[any](),
|
|
|
|
workqueue.TypedRateLimitingQueueConfig[any]{Name: controllerName},
|
|
|
|
)
|
2022-12-15 09:34:44 +01:00
|
|
|
c := controller{
|
2024-09-04 16:29:59 +05:30
|
|
|
vwcClient: vwcClient,
|
|
|
|
vwcLister: vwcInformer.Lister(),
|
|
|
|
secretLister: secretInformer.Lister().Secrets(config.KyvernoNamespace()),
|
|
|
|
deploymentLister: deploymentInformer.Lister().Deployments(config.KyvernoNamespace()),
|
|
|
|
queue: queue,
|
|
|
|
controllerName: controllerName,
|
|
|
|
logger: logging.ControllerLogger(controllerName),
|
|
|
|
webhookName: webhookName,
|
|
|
|
path: path,
|
|
|
|
server: server,
|
|
|
|
servicePort: servicePort,
|
|
|
|
rules: rules,
|
|
|
|
failurePolicy: failurePolicy,
|
|
|
|
sideEffects: sideEffects,
|
|
|
|
configuration: configuration,
|
|
|
|
labelSelector: labelSelector,
|
|
|
|
caSecretName: caSecretName,
|
|
|
|
runtime: runtime,
|
|
|
|
autoDeleteWebhooks: autoDeleteWebhooks,
|
|
|
|
webhookCleanupSetup: webhookCleanupSetup,
|
|
|
|
postWebhookCleanup: postWebhookCleanup,
|
2022-12-15 09:34:44 +01:00
|
|
|
}
|
2023-08-31 22:08:29 +02:00
|
|
|
if _, _, err := controllerutils.AddDefaultEventHandlers(c.logger, vwcInformer.Informer(), queue); err != nil {
|
2023-09-14 16:52:24 +03:00
|
|
|
c.logger.Error(err, "failed to register event handlers")
|
2023-08-31 22:08:29 +02:00
|
|
|
}
|
|
|
|
if _, err := controllerutils.AddEventHandlersT(
|
2022-12-15 09:34:44 +01:00
|
|
|
secretInformer.Informer(),
|
|
|
|
func(obj *corev1.Secret) {
|
2023-08-28 16:05:49 +02:00
|
|
|
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == caSecretName {
|
2022-12-15 09:34:44 +01:00
|
|
|
c.enqueue()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
func(_, obj *corev1.Secret) {
|
2023-08-28 16:05:49 +02:00
|
|
|
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == caSecretName {
|
2022-12-15 09:34:44 +01:00
|
|
|
c.enqueue()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
func(obj *corev1.Secret) {
|
2023-08-28 16:05:49 +02:00
|
|
|
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == caSecretName {
|
2022-12-15 09:34:44 +01:00
|
|
|
c.enqueue()
|
|
|
|
}
|
|
|
|
},
|
2023-08-31 22:08:29 +02:00
|
|
|
); err != nil {
|
2023-09-14 16:52:24 +03:00
|
|
|
c.logger.Error(err, "failed to register event handlers")
|
2023-08-31 22:08:29 +02:00
|
|
|
}
|
2024-09-04 16:29:59 +05:30
|
|
|
if autoDeleteWebhooks {
|
|
|
|
if _, err := controllerutils.AddEventHandlersT(
|
|
|
|
deploymentInformer.Informer(),
|
|
|
|
func(obj *appsv1.Deployment) {
|
|
|
|
},
|
|
|
|
func(_, obj *appsv1.Deployment) {
|
|
|
|
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == config.KyvernoDeploymentName() {
|
|
|
|
c.enqueueCleanupAfter(1 * time.Second)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
func(obj *appsv1.Deployment) {
|
|
|
|
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == config.KyvernoDeploymentName() {
|
|
|
|
c.enqueueCleanup()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
); err != nil {
|
|
|
|
c.logger.Error(err, "failed to register event handlers")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-06 21:13:32 +02:00
|
|
|
configuration.OnChanged(c.enqueue)
|
2022-12-15 09:34:44 +01:00
|
|
|
return &c
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *controller) Run(ctx context.Context, workers int) {
|
2024-09-11 12:08:45 +05:30
|
|
|
if c.autoDeleteWebhooks {
|
|
|
|
if err := c.webhookCleanupSetup(ctx, c.logger); err != nil {
|
|
|
|
c.logger.Error(err, "failed to setup webhook cleanup")
|
|
|
|
}
|
2024-09-04 16:29:59 +05:30
|
|
|
}
|
2022-12-15 09:34:44 +01:00
|
|
|
c.enqueue()
|
|
|
|
controllerutils.Run(ctx, c.logger, c.controllerName, time.Second, c.queue, workers, maxRetries, c.reconcile)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *controller) enqueue() {
|
|
|
|
c.queue.Add(c.webhookName)
|
|
|
|
}
|
|
|
|
|
2024-09-04 16:29:59 +05:30
|
|
|
func (c *controller) enqueueCleanup() {
|
|
|
|
c.queue.Add(config.KyvernoDeploymentName())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *controller) enqueueCleanupAfter(duration time.Duration) {
|
|
|
|
c.queue.AddAfter(config.KyvernoDeploymentName(), duration)
|
|
|
|
}
|
|
|
|
|
2022-12-15 09:34:44 +01:00
|
|
|
func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, _, _ string) error {
|
2024-09-04 16:29:59 +05:30
|
|
|
if c.autoDeleteWebhooks && c.runtime.IsGoingDown() {
|
|
|
|
return c.reconcileWebhookDeletion(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.autoDeleteWebhooks && key == config.KyvernoDeploymentName() {
|
|
|
|
return c.reconcileWebhookDeletion(ctx)
|
|
|
|
}
|
2022-12-15 09:34:44 +01:00
|
|
|
if key != c.webhookName {
|
|
|
|
return nil
|
|
|
|
}
|
2023-08-28 16:05:49 +02:00
|
|
|
caData, err := tls.ReadRootCASecret(c.caSecretName, config.KyvernoNamespace(), c.secretLister)
|
2022-12-15 09:34:44 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-04-06 21:13:32 +02:00
|
|
|
desired, err := c.build(c.configuration, caData)
|
2022-12-15 09:34:44 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
observed, err := c.vwcLister.Get(desired.Name)
|
|
|
|
if err != nil {
|
|
|
|
if apierrors.IsNotFound(err) {
|
|
|
|
_, err := c.vwcClient.Create(ctx, desired, metav1.CreateOptions{})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = controllerutils.Update(ctx, observed, c.vwcClient, func(w *admissionregistrationv1.ValidatingWebhookConfiguration) error {
|
|
|
|
w.Labels = desired.Labels
|
2023-03-15 14:17:37 +01:00
|
|
|
w.Annotations = desired.Annotations
|
2022-12-15 09:34:44 +01:00
|
|
|
w.OwnerReferences = desired.OwnerReferences
|
|
|
|
w.Webhooks = desired.Webhooks
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-09-04 16:29:59 +05:30
|
|
|
func (c *controller) reconcileWebhookDeletion(ctx context.Context) error {
|
|
|
|
if c.autoDeleteWebhooks {
|
|
|
|
if c.runtime.IsGoingDown() {
|
|
|
|
if c.webhooksDeleted {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
c.webhooksDeleted = true
|
|
|
|
if err := c.vwcClient.Delete(ctx, c.webhookName, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
|
|
|
c.logger.Error(err, "failed to clean up validating webhook configuration", "label", kyverno.LabelWebhookManagedBy)
|
|
|
|
return err
|
|
|
|
} else if err == nil {
|
|
|
|
c.logger.Info("successfully deleted validating webhook configurations", "label", kyverno.LabelWebhookManagedBy)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := c.postWebhookCleanup(ctx, c.logger); err != nil {
|
|
|
|
c.logger.Error(err, "failed to clean up temporary rbac")
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
c.logger.Info("successfully deleted temporary rbac")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := c.webhookCleanupSetup(ctx, c.logger); err != nil {
|
|
|
|
c.logger.Error(err, "failed to reconcile webhook cleanup setup")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.logger.Info("reconciled webhook cleanup setup")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-07 13:58:31 +01:00
|
|
|
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)
|
2022-12-15 09:34:44 +01:00
|
|
|
return metav1.ObjectMeta{
|
2023-12-07 13:58:31 +01:00
|
|
|
Name: name,
|
|
|
|
Labels: desiredLabels,
|
2023-03-15 14:17:37 +01:00
|
|
|
Annotations: annotations,
|
2022-12-15 09:34:44 +01:00
|
|
|
OwnerReferences: owner,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-15 14:17:37 +01:00
|
|
|
func (c *controller) build(cfg config.Configuration, caBundle []byte) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) {
|
2022-12-15 09:34:44 +01:00
|
|
|
return &admissionregistrationv1.ValidatingWebhookConfiguration{
|
2023-12-07 13:58:31 +01:00
|
|
|
ObjectMeta: objectMeta(c.webhookName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels()),
|
2022-12-15 09:34:44 +01:00
|
|
|
Webhooks: []admissionregistrationv1.ValidatingWebhook{{
|
|
|
|
Name: fmt.Sprintf("%s.%s.svc", config.KyvernoServiceName(), config.KyvernoNamespace()),
|
|
|
|
ClientConfig: c.clientConfig(caBundle),
|
|
|
|
Rules: c.rules,
|
|
|
|
FailurePolicy: c.failurePolicy,
|
|
|
|
SideEffects: c.sideEffects,
|
|
|
|
AdmissionReviewVersions: []string{"v1"},
|
2023-08-02 20:17:40 +05:30
|
|
|
ObjectSelector: c.labelSelector,
|
2023-08-18 10:32:15 +02:00
|
|
|
MatchConditions: cfg.GetMatchConditions(),
|
2022-12-15 09:34:44 +01:00
|
|
|
}},
|
|
|
|
},
|
|
|
|
nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *controller) clientConfig(caBundle []byte) admissionregistrationv1.WebhookClientConfig {
|
|
|
|
clientConfig := admissionregistrationv1.WebhookClientConfig{
|
|
|
|
CABundle: caBundle,
|
|
|
|
}
|
|
|
|
if c.server == "" {
|
|
|
|
clientConfig.Service = &admissionregistrationv1.ServiceReference{
|
|
|
|
Namespace: config.KyvernoNamespace(),
|
|
|
|
Name: config.KyvernoServiceName(),
|
|
|
|
Path: &c.path,
|
2023-01-29 00:54:29 +01:00
|
|
|
Port: &c.servicePort,
|
2022-12-15 09:34:44 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
url := fmt.Sprintf("https://%s%s", c.server, c.path)
|
|
|
|
clientConfig.URL = &url
|
|
|
|
}
|
|
|
|
return clientConfig
|
|
|
|
}
|