mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-28 02:18:15 +00:00
refactor: manage webhooks with webhook controller (#4846)
* refactor: add config support to webhook controller Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * refactor: add client config to webhook controller Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * migrate verify webhook Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * v1 Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * refactor: move policy webhooks management in webhook controller Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * policy validating webhook config Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * watch policies Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * refactor: migrate resource webhook management in webhook controller Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * mutating webhook Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * auto update Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * cleanup Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * auto update and wildcard policies Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * policy readiness Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix: can't use v1 admission Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * reduce reconcile Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * watchdog Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * cleanup Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * health check Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * runtime utils Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * runtime utils Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * cleanup Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * watchdog check Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * remove delete from mutating webhook Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * cleanup Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
parent
7d897016e9
commit
4aed9359cb
20 changed files with 1087 additions and 2339 deletions
|
@ -83,6 +83,11 @@ func (p *ClusterPolicy) GetSpec() *Spec {
|
|||
return &p.Spec
|
||||
}
|
||||
|
||||
// GetStatus returns the policy status
|
||||
func (p *ClusterPolicy) GetStatus() *PolicyStatus {
|
||||
return &p.Status
|
||||
}
|
||||
|
||||
// IsNamespaced indicates if the policy is namespace scoped
|
||||
func (p *ClusterPolicy) IsNamespaced() bool {
|
||||
return p.GetNamespace() != ""
|
||||
|
|
|
@ -14,6 +14,7 @@ type PolicyInterface interface {
|
|||
HasAutoGenAnnotation() bool
|
||||
IsNamespaced() bool
|
||||
GetSpec() *Spec
|
||||
GetStatus() *PolicyStatus
|
||||
Validate(sets.String) field.ErrorList
|
||||
GetKind() string
|
||||
CreateDeepCopy() PolicyInterface
|
||||
|
|
|
@ -84,6 +84,11 @@ func (p *Policy) GetSpec() *Spec {
|
|||
return &p.Spec
|
||||
}
|
||||
|
||||
// GetStatus returns the policy status
|
||||
func (p *Policy) GetStatus() *PolicyStatus {
|
||||
return &p.Status
|
||||
}
|
||||
|
||||
// IsNamespaced indicates if the policy is namespace scoped
|
||||
func (p *Policy) IsNamespaced() bool {
|
||||
return true
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions"
|
||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||
kyvernoclient "github.com/kyverno/kyverno/pkg/clients/wrappers"
|
||||
"github.com/kyverno/kyverno/pkg/common"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/controllers/certmanager"
|
||||
configcontroller "github.com/kyverno/kyverno/pkg/controllers/config"
|
||||
|
@ -44,14 +43,15 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/toggle"
|
||||
"github.com/kyverno/kyverno/pkg/tracing"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
runtimeutils "github.com/kyverno/kyverno/pkg/utils/runtime"
|
||||
"github.com/kyverno/kyverno/pkg/version"
|
||||
"github.com/kyverno/kyverno/pkg/webhookconfig"
|
||||
"github.com/kyverno/kyverno/pkg/webhooks"
|
||||
webhookspolicy "github.com/kyverno/kyverno/pkg/webhooks/policy"
|
||||
webhooksresource "github.com/kyverno/kyverno/pkg/webhooks/resource"
|
||||
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/updaterequest"
|
||||
_ "go.uber.org/automaxprocs" // #nosec
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
coordinationv1 "k8s.io/api/coordination/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
@ -99,7 +99,7 @@ var (
|
|||
func parseFlags() error {
|
||||
logging.Init(nil)
|
||||
flag.StringVar(&logFormat, "loggingFormat", logging.TextFormat, "This determines the output format of the logger.")
|
||||
flag.IntVar(&webhookTimeout, "webhookTimeout", int(webhookconfig.DefaultWebhookTimeout), "Timeout for webhook configurations.")
|
||||
flag.IntVar(&webhookTimeout, "webhookTimeout", webhookcontroller.DefaultWebhookTimeout, "Timeout for webhook configurations.")
|
||||
flag.IntVar(&genWorkers, "genWorkers", 10, "Workers for generate controller.")
|
||||
flag.IntVar(&maxQueuedEvents, "maxQueuedEvents", 1000, "Maximum events to be queued.")
|
||||
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
|
||||
|
@ -414,6 +414,7 @@ func createrLeaderControllers(
|
|||
metricsConfig *metrics.MetricsConfig,
|
||||
eventGenerator event.Interface,
|
||||
certRenewer *tls.CertRenewer,
|
||||
runtime runtimeutils.Runtime,
|
||||
) ([]controller, error) {
|
||||
policyCtrl, err := policy.NewPolicyController(
|
||||
kyvernoClient,
|
||||
|
@ -436,6 +437,7 @@ func createrLeaderControllers(
|
|||
certRenewer,
|
||||
)
|
||||
webhookController := webhookcontroller.NewController(
|
||||
dynamicClient.Discovery(),
|
||||
metrics.ObjectClient[*corev1.Secret](
|
||||
metrics.NamespacedClientQueryRecorder(metricsConfig, config.KyvernoNamespace(), "Secret", metrics.KubeClient),
|
||||
kubeClient.CoreV1().Secrets(config.KyvernoNamespace()),
|
||||
|
@ -448,10 +450,22 @@ func createrLeaderControllers(
|
|||
metrics.ClusteredClientQueryRecorder(metricsConfig, "ValidatingWebhookConfiguration", metrics.KubeClient),
|
||||
kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations(),
|
||||
),
|
||||
kubeKyvernoInformer.Core().V1().Secrets(),
|
||||
kubeKyvernoInformer.Core().V1().ConfigMaps(),
|
||||
metrics.ObjectClient[*coordinationv1.Lease](
|
||||
metrics.ClusteredClientQueryRecorder(metricsConfig, "Lease", metrics.KubeClient),
|
||||
kubeClient.CoordinationV1().Leases(config.KyvernoNamespace()),
|
||||
),
|
||||
kyvernoClient,
|
||||
kubeInformer.Admissionregistration().V1().MutatingWebhookConfigurations(),
|
||||
kubeInformer.Admissionregistration().V1().ValidatingWebhookConfigurations(),
|
||||
kyvernoInformer.Kyverno().V1().ClusterPolicies(),
|
||||
kyvernoInformer.Kyverno().V1().Policies(),
|
||||
kubeKyvernoInformer.Core().V1().Secrets(),
|
||||
kubeKyvernoInformer.Core().V1().ConfigMaps(),
|
||||
kubeKyvernoInformer.Coordination().V1().Leases(),
|
||||
serverIP,
|
||||
int32(webhookTimeout),
|
||||
autoUpdateWebhooks,
|
||||
runtime,
|
||||
)
|
||||
return append(
|
||||
[]controller{
|
||||
|
@ -537,26 +551,8 @@ func main() {
|
|||
kubeInformer := kubeinformers.NewSharedInformerFactory(kubeClient, resyncPeriod)
|
||||
kubeKyvernoInformer := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, resyncPeriod, kubeinformers.WithNamespace(config.KyvernoNamespace()))
|
||||
kyvernoInformer := kyvernoinformer.NewSharedInformerFactory(kyvernoClient, resyncPeriod)
|
||||
webhookCfg := webhookconfig.NewRegister(
|
||||
signalCtx,
|
||||
clientConfig,
|
||||
dynamicClient,
|
||||
kubeClient,
|
||||
kyvernoClient,
|
||||
kubeInformer.Admissionregistration().V1().MutatingWebhookConfigurations(),
|
||||
kubeInformer.Admissionregistration().V1().ValidatingWebhookConfigurations(),
|
||||
kubeKyvernoInformer.Apps().V1().Deployments(),
|
||||
kyvernoInformer.Kyverno().V1().ClusterPolicies(),
|
||||
kyvernoInformer.Kyverno().V1().Policies(),
|
||||
metricsConfig,
|
||||
serverIP,
|
||||
int32(webhookTimeout),
|
||||
autoUpdateWebhooks,
|
||||
logging.GlobalLogger(),
|
||||
)
|
||||
configuration, err := config.NewConfiguration(
|
||||
kubeClient,
|
||||
webhookCfg.UpdateWebhookChan,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to initialize configuration")
|
||||
|
@ -591,12 +587,18 @@ func main() {
|
|||
maxQueuedEvents,
|
||||
logging.WithName("EventGenerator"),
|
||||
)
|
||||
// This controller only subscribe to events, nothing is returned...
|
||||
// this controller only subscribe to events, nothing is returned...
|
||||
policymetricscontroller.NewController(
|
||||
metricsConfig,
|
||||
kyvernoInformer.Kyverno().V1().ClusterPolicies(),
|
||||
kyvernoInformer.Kyverno().V1().Policies(),
|
||||
)
|
||||
runtime := runtimeutils.NewRuntime(
|
||||
logger.WithName("runtime"),
|
||||
serverIP,
|
||||
kubeKyvernoInformer.Coordination().V1().Leases(),
|
||||
kubeKyvernoInformer.Apps().V1().Deployments(),
|
||||
)
|
||||
// create non leader controllers
|
||||
nonLeaderControllers, nonLeaderBootstrap := createNonLeaderControllers(
|
||||
kubeInformer,
|
||||
|
@ -640,10 +642,10 @@ func main() {
|
|||
// when losing the lead we just terminate the pod
|
||||
defer signalCancel()
|
||||
// validate config
|
||||
if err := webhookCfg.ValidateWebhookConfigurations(config.KyvernoNamespace(), config.KyvernoConfigMapName()); err != nil {
|
||||
logger.Error(err, "invalid format of the Kyverno init ConfigMap, please correct the format of 'data.webhooks'")
|
||||
os.Exit(1)
|
||||
}
|
||||
// if err := webhookCfg.ValidateWebhookConfigurations(config.KyvernoNamespace(), config.KyvernoConfigMapName()); err != nil {
|
||||
// logger.Error(err, "invalid format of the Kyverno init ConfigMap, please correct the format of 'data.webhooks'")
|
||||
// os.Exit(1)
|
||||
// }
|
||||
// create leader factories
|
||||
kubeInformer := kubeinformers.NewSharedInformerFactory(kubeClient, resyncPeriod)
|
||||
kubeKyvernoInformer := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, resyncPeriod, kubeinformers.WithNamespace(config.KyvernoNamespace()))
|
||||
|
@ -662,6 +664,7 @@ func main() {
|
|||
metricsConfig,
|
||||
eventGenerator,
|
||||
certRenewer,
|
||||
runtime,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to create leader controllers")
|
||||
|
@ -681,16 +684,6 @@ func main() {
|
|||
for _, controller := range leaderControllers {
|
||||
go controller.run(signalCtx, logger.WithName("controllers"))
|
||||
}
|
||||
// bootstrap
|
||||
if autoUpdateWebhooks {
|
||||
go webhookCfg.UpdateWebhookConfigurations(configuration)
|
||||
}
|
||||
registerWrapperRetry := common.RetryFunc(time.Second, webhookRegistrationTimeout, webhookCfg.Register, "failed to register webhook", logger)
|
||||
if err := registerWrapperRetry(); err != nil {
|
||||
logger.Error(err, "timeout registering admission control webhooks")
|
||||
os.Exit(1)
|
||||
}
|
||||
webhookCfg.UpdateWebhookChan <- true
|
||||
// wait until we loose the lead (or signal context is canceled)
|
||||
<-ctx.Done()
|
||||
},
|
||||
|
@ -702,16 +695,6 @@ func main() {
|
|||
}
|
||||
// start leader election
|
||||
go le.Run(signalCtx)
|
||||
// create monitor
|
||||
webhookMonitor, err := webhookconfig.NewMonitor(kubeClient, logging.GlobalLogger())
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to initialize webhookMonitor")
|
||||
os.Exit(1)
|
||||
}
|
||||
// start monitor (only when running in cluster)
|
||||
if serverIP == "" {
|
||||
go webhookMonitor.Run(signalCtx, webhookCfg, certRenewer, eventGenerator)
|
||||
}
|
||||
// create webhooks server
|
||||
urgen := webhookgenerate.NewGenerator(
|
||||
kyvernoClient,
|
||||
|
@ -740,6 +723,7 @@ func main() {
|
|||
server := webhooks.NewServer(
|
||||
policyHandlers,
|
||||
resourceHandlers,
|
||||
configuration,
|
||||
func() ([]byte, []byte, error) {
|
||||
secret, err := secretLister.Secrets(config.KyvernoNamespace()).Get(tls.GenerateTLSPairSecretName())
|
||||
if err != nil {
|
||||
|
@ -747,9 +731,19 @@ func main() {
|
|||
}
|
||||
return secret.Data[corev1.TLSCertKey], secret.Data[corev1.TLSPrivateKeyKey], nil
|
||||
},
|
||||
configuration,
|
||||
webhookCfg,
|
||||
webhookMonitor,
|
||||
metrics.ObjectClient[*admissionregistrationv1.MutatingWebhookConfiguration](
|
||||
metrics.ClusteredClientQueryRecorder(metricsConfig, "MutatingWebhookConfiguration", metrics.KubeClient),
|
||||
kubeClient.AdmissionregistrationV1().MutatingWebhookConfigurations(),
|
||||
),
|
||||
metrics.ObjectClient[*admissionregistrationv1.ValidatingWebhookConfiguration](
|
||||
metrics.ClusteredClientQueryRecorder(metricsConfig, "ValidatingWebhookConfiguration", metrics.KubeClient),
|
||||
kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations(),
|
||||
),
|
||||
metrics.ObjectClient[*coordinationv1.Lease](
|
||||
metrics.ClusteredClientQueryRecorder(metricsConfig, "Lease", metrics.KubeClient),
|
||||
kubeClient.CoordinationV1().Leases(config.KyvernoNamespace()),
|
||||
),
|
||||
runtime,
|
||||
)
|
||||
// start informers and wait for cache sync
|
||||
// we need to call start again because we potentially registered new informers
|
||||
|
|
|
@ -18,32 +18,22 @@ import (
|
|||
const (
|
||||
// MutatingWebhookConfigurationName default resource mutating webhook configuration name
|
||||
MutatingWebhookConfigurationName = "kyverno-resource-mutating-webhook-cfg"
|
||||
// MutatingWebhookConfigurationDebugName default resource mutating webhook configuration name for debug mode
|
||||
MutatingWebhookConfigurationDebugName = "kyverno-resource-mutating-webhook-cfg-debug"
|
||||
// MutatingWebhookName default resource mutating webhook name
|
||||
MutatingWebhookName = "mutate.kyverno.svc"
|
||||
// ValidatingWebhookConfigurationName ...
|
||||
ValidatingWebhookConfigurationName = "kyverno-resource-validating-webhook-cfg"
|
||||
// ValidatingWebhookConfigurationDebugName ...
|
||||
ValidatingWebhookConfigurationDebugName = "kyverno-resource-validating-webhook-cfg-debug"
|
||||
// ValidatingWebhookName ...
|
||||
ValidatingWebhookName = "validate.kyverno.svc"
|
||||
// VerifyMutatingWebhookConfigurationName default verify mutating webhook configuration name
|
||||
VerifyMutatingWebhookConfigurationName = "kyverno-verify-mutating-webhook-cfg"
|
||||
// VerifyMutatingWebhookConfigurationDebugName default verify mutating webhook configuration name for debug mode
|
||||
VerifyMutatingWebhookConfigurationDebugName = "kyverno-verify-mutating-webhook-cfg-debug"
|
||||
// VerifyMutatingWebhookName default verify mutating webhook name
|
||||
VerifyMutatingWebhookName = "monitor-webhooks.kyverno.svc"
|
||||
// PolicyValidatingWebhookConfigurationName default policy validating webhook configuration name
|
||||
PolicyValidatingWebhookConfigurationName = "kyverno-policy-validating-webhook-cfg"
|
||||
// PolicyValidatingWebhookConfigurationDebugName default policy validating webhook configuration name for debug mode
|
||||
PolicyValidatingWebhookConfigurationDebugName = "kyverno-policy-validating-webhook-cfg-debug"
|
||||
// PolicyValidatingWebhookName default policy validating webhook name
|
||||
PolicyValidatingWebhookName = "validate-policy.kyverno.svc"
|
||||
// PolicyMutatingWebhookConfigurationName default policy mutating webhook configuration name
|
||||
PolicyMutatingWebhookConfigurationName = "kyverno-policy-mutating-webhook-cfg"
|
||||
// PolicyMutatingWebhookConfigurationDebugName default policy mutating webhook configuration name for debug mode
|
||||
PolicyMutatingWebhookConfigurationDebugName = "kyverno-policy-mutating-webhook-cfg-debug"
|
||||
// PolicyMutatingWebhookName default policy mutating webhook name
|
||||
PolicyMutatingWebhookName = "mutate-policy.kyverno.svc"
|
||||
// Due to kubernetes issue, we must use next literal constants instead of deployment TypeMeta fields
|
||||
|
@ -139,21 +129,19 @@ type configuration struct {
|
|||
restrictDevelopmentUsername []string
|
||||
webhooks []WebhookConfig
|
||||
generateSuccessEvents bool
|
||||
updateWebhookConfigurations chan<- bool
|
||||
}
|
||||
|
||||
// NewConfiguration ...
|
||||
func NewDefaultConfiguration(updateWebhookConfigurations chan<- bool) *configuration {
|
||||
func NewDefaultConfiguration() *configuration {
|
||||
return &configuration{
|
||||
updateWebhookConfigurations: updateWebhookConfigurations,
|
||||
restrictDevelopmentUsername: []string{"minikube-user", "kubernetes-admin"},
|
||||
excludeGroupRole: defaultExcludeGroupRole,
|
||||
}
|
||||
}
|
||||
|
||||
// NewConfiguration ...
|
||||
func NewConfiguration(client kubernetes.Interface, updateWebhookConfigurations chan<- bool) (Configuration, error) {
|
||||
cd := NewDefaultConfiguration(updateWebhookConfigurations)
|
||||
func NewConfiguration(client kubernetes.Interface) (Configuration, error) {
|
||||
cd := NewDefaultConfiguration()
|
||||
if cm, err := client.CoreV1().ConfigMaps(kyvernoNamespace).Get(context.TODO(), kyvernoConfigMapName, metav1.GetOptions{}); err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
|
@ -232,9 +220,6 @@ func (cd *configuration) Load(cm *corev1.ConfigMap) {
|
|||
}
|
||||
if updateWebhook {
|
||||
logger.Info("webhook configurations changed, updating webhook configurations")
|
||||
if cd.updateWebhookConfigurations != nil {
|
||||
cd.updateWebhookConfigurations <- true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,139 +1,283 @@
|
|||
package background
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/autogen"
|
||||
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||
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/tls"
|
||||
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
runtimeutils "github.com/kyverno/kyverno/pkg/utils/runtime"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
coordinationv1 "k8s.io/api/coordination/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
admissionregistrationv1informers "k8s.io/client-go/informers/admissionregistration/v1"
|
||||
coordinationv1informers "k8s.io/client-go/informers/coordination/v1"
|
||||
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||
admissionregistrationv1listers "k8s.io/client-go/listers/admissionregistration/v1"
|
||||
coordinationv1listers "k8s.io/client-go/listers/coordination/v1"
|
||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
)
|
||||
|
||||
const (
|
||||
// Workers is the number of workers for this controller
|
||||
Workers = 2
|
||||
ControllerName = "webhook-ca-controller"
|
||||
maxRetries = 10
|
||||
Workers = 2
|
||||
ControllerName = "webhook-controller"
|
||||
DefaultWebhookTimeout = 10
|
||||
AnnotationLastRequestTime = "kyverno.io/last-request-time"
|
||||
IdleDeadline = tickerInterval * 5
|
||||
maxRetries = 10
|
||||
managedByLabel = "webhook.kyverno.io/managed-by"
|
||||
tickerInterval = 30 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
noneOnDryRun = admissionregistrationv1.SideEffectClassNoneOnDryRun
|
||||
ifNeeded = admissionregistrationv1.IfNeededReinvocationPolicy
|
||||
ignore = admissionregistrationv1.Ignore
|
||||
fail = admissionregistrationv1.Fail
|
||||
policyRule = admissionregistrationv1.Rule{
|
||||
Resources: []string{"clusterpolicies/*", "policies/*"},
|
||||
APIGroups: []string{"kyverno.io"},
|
||||
APIVersions: []string{"v1", "v2beta1"},
|
||||
}
|
||||
verifyRule = admissionregistrationv1.Rule{
|
||||
Resources: []string{"leases"},
|
||||
APIGroups: []string{"coordination.k8s.io"},
|
||||
APIVersions: []string{"v1"},
|
||||
}
|
||||
)
|
||||
|
||||
type controller struct {
|
||||
// clients
|
||||
secretClient controllerutils.GetClient[*corev1.Secret]
|
||||
mwcClient controllerutils.UpdateClient[*admissionregistrationv1.MutatingWebhookConfiguration]
|
||||
vwcClient controllerutils.UpdateClient[*admissionregistrationv1.ValidatingWebhookConfiguration]
|
||||
discoveryClient dclient.IDiscovery
|
||||
secretClient controllerutils.GetClient[*corev1.Secret]
|
||||
mwcClient controllerutils.ObjectClient[*admissionregistrationv1.MutatingWebhookConfiguration]
|
||||
vwcClient controllerutils.ObjectClient[*admissionregistrationv1.ValidatingWebhookConfiguration]
|
||||
leaseClient controllerutils.UpdateClient[*coordinationv1.Lease]
|
||||
kyvernoClient versioned.Interface
|
||||
|
||||
// listers
|
||||
secretLister corev1listers.SecretLister
|
||||
configMapLister corev1listers.ConfigMapLister
|
||||
mwcLister admissionregistrationv1listers.MutatingWebhookConfigurationLister
|
||||
vwcLister admissionregistrationv1listers.ValidatingWebhookConfigurationLister
|
||||
cpolLister kyvernov1listers.ClusterPolicyLister
|
||||
polLister kyvernov1listers.PolicyLister
|
||||
secretLister corev1listers.SecretLister
|
||||
configMapLister corev1listers.ConfigMapLister
|
||||
leaseLister coordinationv1listers.LeaseLister
|
||||
|
||||
// queue
|
||||
queue workqueue.RateLimitingInterface
|
||||
mwcEnqueue controllerutils.EnqueueFunc
|
||||
vwcEnqueue controllerutils.EnqueueFunc
|
||||
queue workqueue.RateLimitingInterface
|
||||
|
||||
// config
|
||||
server string
|
||||
defaultTimeout int32
|
||||
autoUpdateWebhooks bool
|
||||
runtime runtimeutils.Runtime
|
||||
|
||||
// state
|
||||
lock sync.RWMutex
|
||||
policyState map[string]sets.String
|
||||
}
|
||||
|
||||
func NewController(
|
||||
discoveryClient dclient.IDiscovery,
|
||||
secretClient controllerutils.GetClient[*corev1.Secret],
|
||||
mwcClient controllerutils.UpdateClient[*admissionregistrationv1.MutatingWebhookConfiguration],
|
||||
vwcClient controllerutils.UpdateClient[*admissionregistrationv1.ValidatingWebhookConfiguration],
|
||||
secretInformer corev1informers.SecretInformer,
|
||||
configMapInformer corev1informers.ConfigMapInformer,
|
||||
mwcClient controllerutils.ObjectClient[*admissionregistrationv1.MutatingWebhookConfiguration],
|
||||
vwcClient controllerutils.ObjectClient[*admissionregistrationv1.ValidatingWebhookConfiguration],
|
||||
leaseClient controllerutils.UpdateClient[*coordinationv1.Lease],
|
||||
kyvernoClient versioned.Interface,
|
||||
mwcInformer admissionregistrationv1informers.MutatingWebhookConfigurationInformer,
|
||||
vwcInformer admissionregistrationv1informers.ValidatingWebhookConfigurationInformer,
|
||||
cpolInformer kyvernov1informers.ClusterPolicyInformer,
|
||||
polInformer kyvernov1informers.PolicyInformer,
|
||||
secretInformer corev1informers.SecretInformer,
|
||||
configMapInformer corev1informers.ConfigMapInformer,
|
||||
leaseInformer coordinationv1informers.LeaseInformer,
|
||||
server string,
|
||||
defaultTimeout int32,
|
||||
autoUpdateWebhooks bool,
|
||||
runtime runtimeutils.Runtime,
|
||||
) controllers.Controller {
|
||||
queue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), ControllerName)
|
||||
c := controller{
|
||||
secretClient: secretClient,
|
||||
mwcClient: mwcClient,
|
||||
vwcClient: vwcClient,
|
||||
secretLister: secretInformer.Lister(),
|
||||
configMapLister: configMapInformer.Lister(),
|
||||
mwcLister: mwcInformer.Lister(),
|
||||
vwcLister: vwcInformer.Lister(),
|
||||
queue: queue,
|
||||
mwcEnqueue: controllerutils.AddDefaultEventHandlers(logger.V(3), mwcInformer.Informer(), queue),
|
||||
vwcEnqueue: controllerutils.AddDefaultEventHandlers(logger.V(3), vwcInformer.Informer(), queue),
|
||||
discoveryClient: discoveryClient,
|
||||
secretClient: secretClient,
|
||||
mwcClient: mwcClient,
|
||||
vwcClient: vwcClient,
|
||||
leaseClient: leaseClient,
|
||||
kyvernoClient: kyvernoClient,
|
||||
mwcLister: mwcInformer.Lister(),
|
||||
vwcLister: vwcInformer.Lister(),
|
||||
cpolLister: cpolInformer.Lister(),
|
||||
polLister: polInformer.Lister(),
|
||||
secretLister: secretInformer.Lister(),
|
||||
configMapLister: configMapInformer.Lister(),
|
||||
leaseLister: leaseInformer.Lister(),
|
||||
queue: queue,
|
||||
server: server,
|
||||
defaultTimeout: defaultTimeout,
|
||||
autoUpdateWebhooks: autoUpdateWebhooks,
|
||||
runtime: runtime,
|
||||
policyState: map[string]sets.String{
|
||||
config.MutatingWebhookConfigurationName: sets.NewString(),
|
||||
config.ValidatingWebhookConfigurationName: sets.NewString(),
|
||||
},
|
||||
}
|
||||
controllerutils.AddDefaultEventHandlers(logger, mwcInformer.Informer(), queue)
|
||||
controllerutils.AddDefaultEventHandlers(logger, vwcInformer.Informer(), queue)
|
||||
controllerutils.AddEventHandlersT(
|
||||
secretInformer.Informer(),
|
||||
func(obj *corev1.Secret) { c.secretChanged(obj) },
|
||||
func(_, obj *corev1.Secret) { c.secretChanged(obj) },
|
||||
func(obj *corev1.Secret) { c.secretChanged(obj) },
|
||||
func(obj *corev1.Secret) {
|
||||
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == tls.GenerateRootCASecretName() {
|
||||
c.enqueueAll()
|
||||
}
|
||||
},
|
||||
func(_, obj *corev1.Secret) {
|
||||
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == tls.GenerateRootCASecretName() {
|
||||
c.enqueueAll()
|
||||
}
|
||||
},
|
||||
func(obj *corev1.Secret) {
|
||||
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == tls.GenerateRootCASecretName() {
|
||||
c.enqueueAll()
|
||||
}
|
||||
},
|
||||
)
|
||||
controllerutils.AddEventHandlersT(
|
||||
configMapInformer.Informer(),
|
||||
func(obj *corev1.ConfigMap) { c.configMapChanged(obj) },
|
||||
func(_, obj *corev1.ConfigMap) { c.configMapChanged(obj) },
|
||||
func(obj *corev1.ConfigMap) { c.configMapChanged(obj) },
|
||||
func(obj *corev1.ConfigMap) {
|
||||
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == config.KyvernoConfigMapName() {
|
||||
c.enqueueAll()
|
||||
}
|
||||
},
|
||||
func(_, obj *corev1.ConfigMap) {
|
||||
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == config.KyvernoConfigMapName() {
|
||||
c.enqueueAll()
|
||||
}
|
||||
},
|
||||
func(obj *corev1.ConfigMap) {
|
||||
if obj.GetNamespace() == config.KyvernoNamespace() && obj.GetName() == config.KyvernoConfigMapName() {
|
||||
c.enqueueAll()
|
||||
}
|
||||
},
|
||||
)
|
||||
controllerutils.AddEventHandlers(
|
||||
cpolInformer.Informer(),
|
||||
func(interface{}) { c.enqueueResourceWebhooks(0) },
|
||||
func(interface{}, interface{}) { c.enqueueResourceWebhooks(0) },
|
||||
func(interface{}) { c.enqueueResourceWebhooks(0) },
|
||||
)
|
||||
controllerutils.AddEventHandlers(
|
||||
polInformer.Informer(),
|
||||
func(interface{}) { c.enqueueResourceWebhooks(0) },
|
||||
func(interface{}, interface{}) { c.enqueueResourceWebhooks(0) },
|
||||
func(interface{}) { c.enqueueResourceWebhooks(0) },
|
||||
)
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *controller) Run(ctx context.Context, workers int) {
|
||||
controllerutils.Run(ctx, ControllerName, logger.V(3), c.queue, workers, maxRetries, c.reconcile)
|
||||
// add our known webhooks to the queue
|
||||
c.enqueueAll()
|
||||
go c.watchdog(ctx)
|
||||
controllerutils.Run(ctx, ControllerName, logger, c.queue, workers, maxRetries, c.reconcile)
|
||||
}
|
||||
|
||||
func (c *controller) secretChanged(secret *corev1.Secret) {
|
||||
if secret.GetName() == tls.GenerateRootCASecretName() && secret.GetNamespace() == config.KyvernoNamespace() {
|
||||
if err := c.enqueueAll(); err != nil {
|
||||
logger.Error(err, "failed to enqueue on secret change")
|
||||
func (c *controller) watchdog(ctx context.Context) {
|
||||
ticker := time.NewTicker(tickerInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
lease, err := c.getLease()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to get lease")
|
||||
} else {
|
||||
if _, err := controllerutils.Update(
|
||||
ctx,
|
||||
lease,
|
||||
c.leaseClient,
|
||||
func(lease *coordinationv1.Lease) error {
|
||||
if lease.Annotations == nil {
|
||||
lease.Annotations = map[string]string{}
|
||||
}
|
||||
lease.Annotations[AnnotationLastRequestTime] = time.Now().Format(time.RFC3339)
|
||||
if lease.Labels == nil {
|
||||
lease.Labels = map[string]string{}
|
||||
}
|
||||
lease.Labels["app.kubernetes.io/name"] = kyvernov1.ValueKyvernoApp
|
||||
return nil
|
||||
},
|
||||
); err != nil {
|
||||
logger.Error(err, "failed to get lease")
|
||||
}
|
||||
}
|
||||
c.enqueueResourceWebhooks(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *controller) configMapChanged(cm *corev1.ConfigMap) {
|
||||
if cm.GetName() == config.KyvernoConfigMapName() && cm.GetNamespace() == config.KyvernoNamespace() {
|
||||
if err := c.enqueueAll(); err != nil {
|
||||
logger.Error(err, "failed to enqueue on configmap change")
|
||||
}
|
||||
func (c *controller) watchdogCheck() bool {
|
||||
lease, err := c.getLease()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to get lease")
|
||||
return false
|
||||
}
|
||||
annotations := lease.GetAnnotations()
|
||||
if annotations == nil {
|
||||
return false
|
||||
}
|
||||
annTime, err := time.Parse(time.RFC3339, annotations[AnnotationLastRequestTime])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return time.Now().Before(annTime.Add(IdleDeadline))
|
||||
}
|
||||
|
||||
func (c *controller) enqueueAll() error {
|
||||
requirement, err := labels.NewRequirement("webhook.kyverno.io/managed-by", selection.Equals, []string{kyvernov1.ValueKyvernoApp})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
selector := labels.Everything().Add(*requirement)
|
||||
mwcs, err := c.mwcLister.List(selector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, mwc := range mwcs {
|
||||
err = c.mwcEnqueue(mwc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
vwcs, err := c.vwcLister.List(selector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, vwc := range vwcs {
|
||||
err = c.vwcEnqueue(vwc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func (c *controller) enqueueAll() {
|
||||
c.enqueuePolicyWebhooks()
|
||||
c.enqueueResourceWebhooks(0)
|
||||
c.enqueueVerifyWebhook()
|
||||
}
|
||||
|
||||
func (c *controller) enqueuePolicyWebhooks() {
|
||||
c.queue.Add(config.PolicyValidatingWebhookConfigurationName)
|
||||
c.queue.Add(config.PolicyMutatingWebhookConfigurationName)
|
||||
}
|
||||
|
||||
func (c *controller) enqueueResourceWebhooks(duration time.Duration) {
|
||||
c.queue.AddAfter(config.MutatingWebhookConfigurationName, duration)
|
||||
c.queue.AddAfter(config.ValidatingWebhookConfigurationName, duration)
|
||||
}
|
||||
|
||||
func (c *controller) enqueueVerifyWebhook() {
|
||||
c.queue.Add(config.VerifyMutatingWebhookConfigurationName)
|
||||
}
|
||||
|
||||
func (c *controller) loadConfig() config.Configuration {
|
||||
cfg := config.NewDefaultConfiguration(nil)
|
||||
cfg := config.NewDefaultConfiguration()
|
||||
cm, err := c.configMapLister.ConfigMaps(config.KyvernoNamespace()).Get(config.KyvernoConfigMapName())
|
||||
if err == nil {
|
||||
cfg.Load(cm)
|
||||
|
@ -141,78 +285,575 @@ func (c *controller) loadConfig() config.Configuration {
|
|||
return cfg
|
||||
}
|
||||
|
||||
func (c *controller) reconcileMutatingWebhookConfiguration(ctx context.Context, logger logr.Logger, name string) error {
|
||||
w, err := c.mwcLister.Get(name)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
func (c *controller) recordPolicyState(webhookConfigurationName string, policies ...kyvernov1.PolicyInterface) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if _, ok := c.policyState[webhookConfigurationName]; !ok {
|
||||
return
|
||||
}
|
||||
c.policyState[webhookConfigurationName] = sets.NewString()
|
||||
for _, policy := range policies {
|
||||
policyKey, err := cache.MetaNamespaceKeyFunc(policy)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to compute policy key", "policy", policy)
|
||||
}
|
||||
return err
|
||||
c.policyState[webhookConfigurationName].Insert(policyKey)
|
||||
}
|
||||
labels := w.GetLabels()
|
||||
if labels == nil || labels["webhook.kyverno.io/managed-by"] != kyvernov1.ValueKyvernoApp {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *controller) clientConfig(caBundle []byte, path string) admissionregistrationv1.WebhookClientConfig {
|
||||
clientConfig := admissionregistrationv1.WebhookClientConfig{
|
||||
CABundle: caBundle,
|
||||
}
|
||||
cfg := c.loadConfig()
|
||||
webhookCfg := config.WebhookConfig{}
|
||||
webhookCfgs := cfg.GetWebhooks()
|
||||
if len(webhookCfgs) > 0 {
|
||||
webhookCfg = webhookCfgs[0]
|
||||
if c.server == "" {
|
||||
clientConfig.Service = &admissionregistrationv1.ServiceReference{
|
||||
Namespace: config.KyvernoNamespace(),
|
||||
Name: config.KyvernoServiceName(),
|
||||
Path: &path,
|
||||
}
|
||||
} else {
|
||||
url := fmt.Sprintf("https://%s%s", c.server, path)
|
||||
clientConfig.URL = &url
|
||||
}
|
||||
return clientConfig
|
||||
}
|
||||
|
||||
func (c *controller) reconcileResourceValidatingWebhookConfiguration(ctx context.Context) error {
|
||||
if c.autoUpdateWebhooks {
|
||||
return c.reconcileValidatingWebhookConfiguration(ctx, c.autoUpdateWebhooks, c.buildResourceValidatingWebhookConfiguration)
|
||||
} else {
|
||||
return c.reconcileValidatingWebhookConfiguration(ctx, c.autoUpdateWebhooks, c.buildDefaultResourceValidatingWebhookConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *controller) reconcileResourceMutatingWebhookConfiguration(ctx context.Context) error {
|
||||
if c.autoUpdateWebhooks {
|
||||
return c.reconcileMutatingWebhookConfiguration(ctx, c.autoUpdateWebhooks, c.buildResourceMutatingWebhookConfiguration)
|
||||
} else {
|
||||
return c.reconcileMutatingWebhookConfiguration(ctx, c.autoUpdateWebhooks, c.buildDefaultResourceMutatingWebhookConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *controller) reconcilePolicyValidatingWebhookConfiguration(ctx context.Context) error {
|
||||
return c.reconcileValidatingWebhookConfiguration(ctx, true, c.buildPolicyValidatingWebhookConfiguration)
|
||||
}
|
||||
|
||||
func (c *controller) reconcilePolicyMutatingWebhookConfiguration(ctx context.Context) error {
|
||||
return c.reconcileMutatingWebhookConfiguration(ctx, true, c.buildPolicyMutatingWebhookConfiguration)
|
||||
}
|
||||
|
||||
func (c *controller) reconcileVerifyMutatingWebhookConfiguration(ctx context.Context) error {
|
||||
return c.reconcileMutatingWebhookConfiguration(ctx, true, c.buildVerifyMutatingWebhookConfiguration)
|
||||
}
|
||||
|
||||
func (c *controller) reconcileValidatingWebhookConfiguration(ctx context.Context, autoUpdateWebhooks bool, build func([]byte) (*admissionregistrationv1.ValidatingWebhookConfiguration, error)) error {
|
||||
caData, err := tls.ReadRootCASecret(c.secretClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = controllerutils.Update(ctx, w, c.mwcClient, func(w *admissionregistrationv1.MutatingWebhookConfiguration) error {
|
||||
for i := range w.Webhooks {
|
||||
w.Webhooks[i].ClientConfig.CABundle = caData
|
||||
w.Webhooks[i].ObjectSelector = webhookCfg.ObjectSelector
|
||||
w.Webhooks[i].NamespaceSelector = webhookCfg.NamespaceSelector
|
||||
desired, err := build(caData)
|
||||
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
|
||||
}
|
||||
if !autoUpdateWebhooks {
|
||||
return nil
|
||||
}
|
||||
_, err = controllerutils.Update(ctx, observed, c.vwcClient, func(w *admissionregistrationv1.ValidatingWebhookConfiguration) error {
|
||||
w.Labels = desired.Labels
|
||||
w.OwnerReferences = desired.OwnerReferences
|
||||
w.Webhooks = desired.Webhooks
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *controller) reconcileValidatingWebhookConfiguration(ctx context.Context, logger logr.Logger, name string) error {
|
||||
w, err := c.vwcLister.Get(name)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
labels := w.GetLabels()
|
||||
if labels == nil || labels["webhook.kyverno.io/managed-by"] != kyvernov1.ValueKyvernoApp {
|
||||
return nil
|
||||
}
|
||||
cfg := c.loadConfig()
|
||||
webhookCfg := config.WebhookConfig{}
|
||||
webhookCfgs := cfg.GetWebhooks()
|
||||
if len(webhookCfgs) > 0 {
|
||||
webhookCfg = webhookCfgs[0]
|
||||
}
|
||||
func (c *controller) reconcileMutatingWebhookConfiguration(ctx context.Context, autoUpdateWebhooks bool, build func([]byte) (*admissionregistrationv1.MutatingWebhookConfiguration, error)) error {
|
||||
caData, err := tls.ReadRootCASecret(c.secretClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = controllerutils.Update(ctx, w, c.vwcClient, func(w *admissionregistrationv1.ValidatingWebhookConfiguration) error {
|
||||
for i := range w.Webhooks {
|
||||
w.Webhooks[i].ClientConfig.CABundle = caData
|
||||
w.Webhooks[i].ObjectSelector = webhookCfg.ObjectSelector
|
||||
w.Webhooks[i].NamespaceSelector = webhookCfg.NamespaceSelector
|
||||
desired, err := build(caData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
observed, err := c.mwcLister.Get(desired.Name)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
_, err := c.mwcClient.Create(ctx, desired, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !autoUpdateWebhooks {
|
||||
return nil
|
||||
}
|
||||
_, err = controllerutils.Update(ctx, observed, c.mwcClient, func(w *admissionregistrationv1.MutatingWebhookConfiguration) error {
|
||||
w.Labels = desired.Labels
|
||||
w.OwnerReferences = desired.OwnerReferences
|
||||
w.Webhooks = desired.Webhooks
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, namespace, name string) error {
|
||||
if err := c.reconcileMutatingWebhookConfiguration(ctx, logger, name); err != nil {
|
||||
func (c *controller) updatePolicyStatuses(ctx context.Context) error {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
policies, err := c.getAllPolicies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.reconcileValidatingWebhookConfiguration(ctx, logger, name); err != nil {
|
||||
return err
|
||||
for _, policy := range policies {
|
||||
policyKey, err := cache.MetaNamespaceKeyFunc(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ready := true
|
||||
for _, set := range c.policyState {
|
||||
if !set.Has(policyKey) {
|
||||
ready = false
|
||||
}
|
||||
}
|
||||
if policy.IsReady() != ready {
|
||||
policy = policy.CreateDeepCopy()
|
||||
status := policy.GetStatus()
|
||||
status.SetReady(ready)
|
||||
if policy.GetNamespace() == "" {
|
||||
_, err := c.kyvernoClient.KyvernoV1().ClusterPolicies().UpdateStatus(ctx, policy.(*kyvernov1.ClusterPolicy), metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err := c.kyvernoClient.KyvernoV1().Policies(policy.GetNamespace()).UpdateStatus(ctx, policy.(*kyvernov1.Policy), metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, namespace, name string) error {
|
||||
switch name {
|
||||
case config.MutatingWebhookConfigurationName:
|
||||
if c.runtime.IsRollingUpdate() {
|
||||
c.enqueueResourceWebhooks(1 * time.Second)
|
||||
} else {
|
||||
if err := c.reconcileResourceMutatingWebhookConfiguration(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.updatePolicyStatuses(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case config.ValidatingWebhookConfigurationName:
|
||||
if c.runtime.IsRollingUpdate() {
|
||||
c.enqueueResourceWebhooks(1 * time.Second)
|
||||
} else {
|
||||
if err := c.reconcileResourceValidatingWebhookConfiguration(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.updatePolicyStatuses(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case config.PolicyValidatingWebhookConfigurationName:
|
||||
return c.reconcilePolicyValidatingWebhookConfiguration(ctx)
|
||||
case config.PolicyMutatingWebhookConfigurationName:
|
||||
return c.reconcilePolicyMutatingWebhookConfiguration(ctx)
|
||||
case config.VerifyMutatingWebhookConfigurationName:
|
||||
return c.reconcileVerifyMutatingWebhookConfiguration(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *controller) buildVerifyMutatingWebhookConfiguration(caBundle []byte) (*admissionregistrationv1.MutatingWebhookConfiguration, error) {
|
||||
return &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: objectMeta(config.VerifyMutatingWebhookConfigurationName),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
||||
Name: config.VerifyMutatingWebhookName,
|
||||
ClientConfig: c.clientConfig(caBundle, config.VerifyMutatingWebhookServicePath),
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Rule: verifyRule,
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Update,
|
||||
},
|
||||
}},
|
||||
FailurePolicy: &ignore,
|
||||
SideEffects: &noneOnDryRun,
|
||||
ReinvocationPolicy: &ifNeeded,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
ObjectSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app.kubernetes.io/name": kyvernov1.ValueKyvernoApp,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
func (c *controller) buildPolicyMutatingWebhookConfiguration(caBundle []byte) (*admissionregistrationv1.MutatingWebhookConfiguration, error) {
|
||||
return &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: objectMeta(config.PolicyMutatingWebhookConfigurationName),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
||||
Name: config.PolicyMutatingWebhookName,
|
||||
ClientConfig: c.clientConfig(caBundle, config.PolicyMutatingWebhookServicePath),
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Rule: policyRule,
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Create,
|
||||
admissionregistrationv1.Update,
|
||||
},
|
||||
}},
|
||||
FailurePolicy: &ignore,
|
||||
SideEffects: &noneOnDryRun,
|
||||
ReinvocationPolicy: &ifNeeded,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
func (c *controller) buildPolicyValidatingWebhookConfiguration(caBundle []byte) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) {
|
||||
return &admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: objectMeta(config.PolicyValidatingWebhookConfigurationName),
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{{
|
||||
Name: config.PolicyValidatingWebhookName,
|
||||
ClientConfig: c.clientConfig(caBundle, config.PolicyValidatingWebhookServicePath),
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Rule: policyRule,
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Create,
|
||||
admissionregistrationv1.Update,
|
||||
},
|
||||
}},
|
||||
FailurePolicy: &ignore,
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
func (c *controller) buildDefaultResourceMutatingWebhookConfiguration(caBundle []byte) (*admissionregistrationv1.MutatingWebhookConfiguration, error) {
|
||||
return &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: objectMeta(config.MutatingWebhookConfigurationName),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{{
|
||||
Name: config.MutatingWebhookName + "-ignore",
|
||||
ClientConfig: c.clientConfig(caBundle, config.MutatingWebhookServicePath+"/ignore"),
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"*"},
|
||||
APIVersions: []string{"*"},
|
||||
Resources: []string{"*/*"},
|
||||
},
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Create,
|
||||
admissionregistrationv1.Update,
|
||||
},
|
||||
}},
|
||||
FailurePolicy: &ignore,
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
TimeoutSeconds: &c.defaultTimeout,
|
||||
ReinvocationPolicy: &ifNeeded,
|
||||
}},
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
func (c *controller) buildResourceMutatingWebhookConfiguration(caBundle []byte) (*admissionregistrationv1.MutatingWebhookConfiguration, error) {
|
||||
result := admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: objectMeta(config.MutatingWebhookConfigurationName),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{},
|
||||
}
|
||||
if c.watchdogCheck() {
|
||||
ignore := newWebhook(c.defaultTimeout, ignore)
|
||||
fail := newWebhook(c.defaultTimeout, fail)
|
||||
policies, err := c.getAllPolicies()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.recordPolicyState(config.MutatingWebhookConfigurationName, policies...)
|
||||
// TODO: shouldn't be per failure policy, depending of the policy/rules that apply ?
|
||||
if hasWildcard(policies...) {
|
||||
ignore.setWildcard()
|
||||
fail.setWildcard()
|
||||
} else {
|
||||
for _, p := range policies {
|
||||
spec := p.GetSpec()
|
||||
if spec.HasMutate() || spec.HasVerifyImages() {
|
||||
if spec.GetFailurePolicy() == kyvernov1.Ignore {
|
||||
c.mergeWebhook(ignore, p, false)
|
||||
} else {
|
||||
c.mergeWebhook(fail, p, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cfg := c.loadConfig()
|
||||
webhookCfg := config.WebhookConfig{}
|
||||
webhookCfgs := cfg.GetWebhooks()
|
||||
if len(webhookCfgs) > 0 {
|
||||
webhookCfg = webhookCfgs[0]
|
||||
}
|
||||
if !ignore.isEmpty() {
|
||||
result.Webhooks = append(
|
||||
result.Webhooks,
|
||||
admissionregistrationv1.MutatingWebhook{
|
||||
Name: config.MutatingWebhookName + "-ignore",
|
||||
ClientConfig: c.clientConfig(caBundle, config.MutatingWebhookServicePath+"/ignore"),
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{
|
||||
ignore.buildRuleWithOperations(admissionregistrationv1.Create, admissionregistrationv1.Update),
|
||||
},
|
||||
FailurePolicy: &ignore.failurePolicy,
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
NamespaceSelector: webhookCfg.NamespaceSelector,
|
||||
ObjectSelector: webhookCfg.ObjectSelector,
|
||||
TimeoutSeconds: &ignore.maxWebhookTimeout,
|
||||
ReinvocationPolicy: &ifNeeded,
|
||||
},
|
||||
)
|
||||
}
|
||||
if !fail.isEmpty() {
|
||||
result.Webhooks = append(
|
||||
result.Webhooks,
|
||||
admissionregistrationv1.MutatingWebhook{
|
||||
Name: config.MutatingWebhookName + "-fail",
|
||||
ClientConfig: c.clientConfig(caBundle, config.MutatingWebhookServicePath+"/fail"),
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{
|
||||
fail.buildRuleWithOperations(admissionregistrationv1.Create, admissionregistrationv1.Update),
|
||||
},
|
||||
FailurePolicy: &fail.failurePolicy,
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
NamespaceSelector: webhookCfg.NamespaceSelector,
|
||||
ObjectSelector: webhookCfg.ObjectSelector,
|
||||
TimeoutSeconds: &fail.maxWebhookTimeout,
|
||||
ReinvocationPolicy: &ifNeeded,
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
c.recordPolicyState(config.MutatingWebhookConfigurationName)
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *controller) buildDefaultResourceValidatingWebhookConfiguration(caBundle []byte) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) {
|
||||
return &admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: objectMeta(config.ValidatingWebhookConfigurationName),
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{{
|
||||
Name: config.ValidatingWebhookName + "-ignore",
|
||||
ClientConfig: c.clientConfig(caBundle, config.ValidatingWebhookServicePath+"/ignore"),
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"*"},
|
||||
APIVersions: []string{"*"},
|
||||
Resources: []string{"*/*"},
|
||||
},
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Create,
|
||||
admissionregistrationv1.Update,
|
||||
admissionregistrationv1.Delete,
|
||||
admissionregistrationv1.Connect,
|
||||
},
|
||||
}},
|
||||
FailurePolicy: &ignore,
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
TimeoutSeconds: &c.defaultTimeout,
|
||||
}},
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
func (c *controller) buildResourceValidatingWebhookConfiguration(caBundle []byte) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) {
|
||||
result := admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: objectMeta(config.ValidatingWebhookConfigurationName),
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{},
|
||||
}
|
||||
if c.watchdogCheck() {
|
||||
ignore := newWebhook(c.defaultTimeout, ignore)
|
||||
fail := newWebhook(c.defaultTimeout, fail)
|
||||
policies, err := c.getAllPolicies()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.recordPolicyState(config.ValidatingWebhookConfigurationName, policies...)
|
||||
// TODO: shouldn't be per failure policy, depending of the policy/rules that apply ?
|
||||
if hasWildcard(policies...) {
|
||||
ignore.setWildcard()
|
||||
fail.setWildcard()
|
||||
} else {
|
||||
for _, p := range policies {
|
||||
spec := p.GetSpec()
|
||||
if spec.HasValidate() || spec.HasGenerate() || spec.HasMutate() || spec.HasImagesValidationChecks() || spec.HasYAMLSignatureVerify() {
|
||||
if spec.GetFailurePolicy() == kyvernov1.Ignore {
|
||||
c.mergeWebhook(ignore, p, true)
|
||||
} else {
|
||||
c.mergeWebhook(fail, p, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cfg := c.loadConfig()
|
||||
webhookCfg := config.WebhookConfig{}
|
||||
webhookCfgs := cfg.GetWebhooks()
|
||||
if len(webhookCfgs) > 0 {
|
||||
webhookCfg = webhookCfgs[0]
|
||||
}
|
||||
if !ignore.isEmpty() {
|
||||
result.Webhooks = append(
|
||||
result.Webhooks,
|
||||
admissionregistrationv1.ValidatingWebhook{
|
||||
Name: config.ValidatingWebhookName + "-ignore",
|
||||
ClientConfig: c.clientConfig(caBundle, config.ValidatingWebhookServicePath+"/ignore"),
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{
|
||||
ignore.buildRuleWithOperations(admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete, admissionregistrationv1.Connect),
|
||||
},
|
||||
FailurePolicy: &ignore.failurePolicy,
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
NamespaceSelector: webhookCfg.NamespaceSelector,
|
||||
ObjectSelector: webhookCfg.ObjectSelector,
|
||||
TimeoutSeconds: &ignore.maxWebhookTimeout,
|
||||
},
|
||||
)
|
||||
}
|
||||
if !fail.isEmpty() {
|
||||
result.Webhooks = append(
|
||||
result.Webhooks,
|
||||
admissionregistrationv1.ValidatingWebhook{
|
||||
Name: config.ValidatingWebhookName + "-fail",
|
||||
ClientConfig: c.clientConfig(caBundle, config.ValidatingWebhookServicePath+"/fail"),
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{
|
||||
fail.buildRuleWithOperations(admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete, admissionregistrationv1.Connect),
|
||||
},
|
||||
FailurePolicy: &fail.failurePolicy,
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
NamespaceSelector: webhookCfg.NamespaceSelector,
|
||||
ObjectSelector: webhookCfg.ObjectSelector,
|
||||
TimeoutSeconds: &fail.maxWebhookTimeout,
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
c.recordPolicyState(config.MutatingWebhookConfigurationName)
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *controller) getAllPolicies() ([]kyvernov1.PolicyInterface, error) {
|
||||
var policies []kyvernov1.PolicyInterface
|
||||
if cpols, err := c.cpolLister.List(labels.Everything()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, cpol := range cpols {
|
||||
policies = append(policies, cpol)
|
||||
}
|
||||
}
|
||||
if pols, err := c.polLister.List(labels.Everything()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, pol := range pols {
|
||||
policies = append(policies, pol)
|
||||
}
|
||||
}
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
func (c *controller) getLease() (*coordinationv1.Lease, error) {
|
||||
return c.leaseLister.Leases(config.KyvernoNamespace()).Get("kyverno")
|
||||
}
|
||||
|
||||
// mergeWebhook merges the matching kinds of the policy to webhook.rule
|
||||
func (c *controller) mergeWebhook(dst *webhook, policy kyvernov1.PolicyInterface, updateValidate bool) {
|
||||
matchedGVK := make([]string, 0)
|
||||
for _, rule := range autogen.ComputeRules(policy) {
|
||||
// matching kinds in generate policies need to be added to both webhook
|
||||
if rule.HasGenerate() {
|
||||
matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...)
|
||||
matchedGVK = append(matchedGVK, rule.Generation.ResourceSpec.Kind)
|
||||
continue
|
||||
}
|
||||
if (updateValidate && rule.HasValidate() || rule.HasImagesValidationChecks()) ||
|
||||
(updateValidate && rule.HasMutate() && rule.IsMutateExisting()) ||
|
||||
(!updateValidate && rule.HasMutate()) && !rule.IsMutateExisting() ||
|
||||
(!updateValidate && rule.HasVerifyImages()) || (!updateValidate && rule.HasYAMLSignatureVerify()) {
|
||||
matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...)
|
||||
}
|
||||
}
|
||||
gvkMap := make(map[string]int)
|
||||
gvrList := make([]schema.GroupVersionResource, 0)
|
||||
for _, gvk := range matchedGVK {
|
||||
if _, ok := gvkMap[gvk]; !ok {
|
||||
gvkMap[gvk] = 1
|
||||
// NOTE: webhook stores GVR in its rules while policy stores GVK in its rules definition
|
||||
gv, k := kubeutils.GetKindFromGVK(gvk)
|
||||
switch k {
|
||||
case "Binding":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/binding"})
|
||||
case "NodeProxyOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nodes/proxy"})
|
||||
case "PodAttachOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/attach"})
|
||||
case "PodExecOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/exec"})
|
||||
case "PodPortForwardOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/portforward"})
|
||||
case "PodProxyOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/proxy"})
|
||||
case "ServiceProxyOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services/proxy"})
|
||||
default:
|
||||
_, gvr, err := c.discoveryClient.FindResource(gv, k)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to convert GVK to GVR", "GVK", gvk)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(gvk, "*") {
|
||||
group := kubeutils.GetGroupFromGVK(gvk)
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: group, Version: "*", Resource: gvr.Resource})
|
||||
} else {
|
||||
logger.V(4).Info("configuring webhook", "GVK", gvk, "GVR", gvr)
|
||||
gvrList = append(gvrList, gvr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, gvr := range gvrList {
|
||||
dst.groups.Insert(gvr.Group)
|
||||
if gvr.Version == "*" {
|
||||
dst.versions = sets.NewString()
|
||||
dst.versions.Insert(gvr.Version)
|
||||
} else if !dst.versions.Has("*") {
|
||||
dst.versions.Insert(gvr.Version)
|
||||
}
|
||||
dst.resources.Insert(gvr.Resource)
|
||||
}
|
||||
if dst.resources.Has("pods") {
|
||||
dst.resources.Insert("pods/ephemeralcontainers")
|
||||
}
|
||||
if dst.resources.Has("services") {
|
||||
dst.resources.Insert("services/status")
|
||||
}
|
||||
spec := policy.GetSpec()
|
||||
if spec.WebhookTimeoutSeconds != nil {
|
||||
if dst.maxWebhookTimeout < *spec.WebhookTimeoutSeconds {
|
||||
dst.maxWebhookTimeout = *spec.WebhookTimeoutSeconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package background
|
||||
package webhook
|
||||
|
||||
import "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
|
|
72
pkg/controllers/webhook/utils.go
Normal file
72
pkg/controllers/webhook/utils.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package webhook
|
||||
|
||||
import (
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// webhook is the instance that aggregates the GVK of existing policies
|
||||
// based on kind, failurePolicy and webhookTimeout
|
||||
type webhook struct {
|
||||
maxWebhookTimeout int32
|
||||
failurePolicy admissionregistrationv1.FailurePolicyType
|
||||
groups sets.String
|
||||
versions sets.String
|
||||
resources sets.String
|
||||
}
|
||||
|
||||
func newWebhook(timeout int32, failurePolicy admissionregistrationv1.FailurePolicyType) *webhook {
|
||||
return &webhook{
|
||||
maxWebhookTimeout: timeout,
|
||||
failurePolicy: failurePolicy,
|
||||
groups: sets.NewString(),
|
||||
versions: sets.NewString(),
|
||||
resources: sets.NewString(),
|
||||
}
|
||||
}
|
||||
|
||||
func (wh *webhook) buildRuleWithOperations(ops ...admissionregistrationv1.OperationType) admissionregistrationv1.RuleWithOperations {
|
||||
return admissionregistrationv1.RuleWithOperations{
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: wh.groups.List(),
|
||||
APIVersions: wh.versions.List(),
|
||||
Resources: wh.resources.List(),
|
||||
},
|
||||
Operations: ops,
|
||||
}
|
||||
}
|
||||
|
||||
func (wh *webhook) isEmpty() bool {
|
||||
return wh.groups.Len() == 0 || wh.versions.Len() == 0 || wh.resources.Len() == 0
|
||||
}
|
||||
|
||||
func (wh *webhook) setWildcard() {
|
||||
wh.groups = sets.NewString("*")
|
||||
wh.versions = sets.NewString("*")
|
||||
wh.resources = sets.NewString("*/*")
|
||||
}
|
||||
|
||||
func hasWildcard(policies ...kyvernov1.PolicyInterface) bool {
|
||||
for _, policy := range policies {
|
||||
spec := policy.GetSpec()
|
||||
for _, rule := range spec.Rules {
|
||||
if kinds := rule.MatchResources.GetKinds(); utils.ContainsString(kinds, "*") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func objectMeta(name string, owner ...metav1.OwnerReference) metav1.ObjectMeta {
|
||||
return metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: map[string]string{
|
||||
managedByLabel: kyvernov1.ValueKyvernoApp,
|
||||
},
|
||||
OwnerReferences: owner,
|
||||
}
|
||||
}
|
16
pkg/controllers/webhook/utils_test.go
Normal file
16
pkg/controllers/webhook/utils_test.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package webhook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
)
|
||||
|
||||
func Test_webhook_isEmpty(t *testing.T) {
|
||||
empty := newWebhook(DefaultWebhookTimeout, admissionregistrationv1.Ignore)
|
||||
assert.Equal(t, empty.isEmpty(), true)
|
||||
notEmpty := newWebhook(DefaultWebhookTimeout, admissionregistrationv1.Ignore)
|
||||
notEmpty.setWildcard()
|
||||
assert.Equal(t, notEmpty.isEmpty(), false)
|
||||
}
|
121
pkg/utils/runtime/utils.go
Normal file
121
pkg/utils/runtime/utils.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
coordinationv1 "k8s.io/api/coordination/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
appsv1informers "k8s.io/client-go/informers/apps/v1"
|
||||
coordinationv1informers "k8s.io/client-go/informers/coordination/v1"
|
||||
appsv1listers "k8s.io/client-go/listers/apps/v1"
|
||||
coordinationv1listers "k8s.io/client-go/listers/coordination/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
AnnotationLastRequestTime = "kyverno.io/last-request-time"
|
||||
IdleDeadline = tickerInterval * 5
|
||||
tickerInterval = 30 * time.Second
|
||||
)
|
||||
|
||||
type Runtime interface {
|
||||
IsDebug() bool
|
||||
IsReady() bool
|
||||
IsLive() bool
|
||||
IsRollingUpdate() bool
|
||||
IsGoingDown() bool
|
||||
}
|
||||
|
||||
type runtime struct {
|
||||
serverIP string
|
||||
leaseLister coordinationv1listers.LeaseLister
|
||||
deploymentLister appsv1listers.DeploymentLister
|
||||
logger logr.Logger
|
||||
}
|
||||
|
||||
func NewRuntime(
|
||||
logger logr.Logger,
|
||||
serverIP string,
|
||||
leaseInformer coordinationv1informers.LeaseInformer,
|
||||
deploymentInformer appsv1informers.DeploymentInformer,
|
||||
) Runtime {
|
||||
return &runtime{
|
||||
serverIP: serverIP,
|
||||
leaseLister: leaseInformer.Lister(),
|
||||
deploymentLister: deploymentInformer.Lister(),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *runtime) IsDebug() bool {
|
||||
return c.serverIP != ""
|
||||
}
|
||||
|
||||
func (c *runtime) IsLive() bool {
|
||||
return c.IsDebug() || c.check()
|
||||
}
|
||||
|
||||
func (c *runtime) IsReady() bool {
|
||||
return c.IsDebug() || c.check()
|
||||
}
|
||||
|
||||
func (c *runtime) IsRollingUpdate() bool {
|
||||
if c.IsDebug() {
|
||||
return false
|
||||
}
|
||||
deployment, err := c.getDeployment()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
var replicas int32 = 1
|
||||
if deployment.Spec.Replicas != nil {
|
||||
replicas = *deployment.Spec.Replicas
|
||||
}
|
||||
nonTerminatedReplicas := deployment.Status.Replicas
|
||||
if nonTerminatedReplicas > replicas {
|
||||
c.logger.Info("detect Kyverno is in rolling update, won't trigger the update again")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *runtime) IsGoingDown() bool {
|
||||
if c.IsDebug() {
|
||||
return false
|
||||
}
|
||||
deployment, err := c.getDeployment()
|
||||
if err != nil {
|
||||
return apierrors.IsNotFound(err)
|
||||
}
|
||||
if deployment.Spec.Replicas != nil {
|
||||
return *deployment.Spec.Replicas == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *runtime) getLease() (*coordinationv1.Lease, error) {
|
||||
return c.leaseLister.Leases(config.KyvernoNamespace()).Get("kyverno")
|
||||
}
|
||||
|
||||
func (c *runtime) getDeployment() (*appsv1.Deployment, error) {
|
||||
return c.deploymentLister.Deployments(config.KyvernoNamespace()).Get("kyverno")
|
||||
}
|
||||
|
||||
func (c *runtime) check() bool {
|
||||
lease, err := c.getLease()
|
||||
if err != nil {
|
||||
c.logger.Error(err, "failed to get lease")
|
||||
return false
|
||||
}
|
||||
annotations := lease.GetAnnotations()
|
||||
if annotations == nil {
|
||||
return false
|
||||
}
|
||||
annTime, err := time.Parse(time.RFC3339, annotations[AnnotationLastRequestTime])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return time.Now().Before(annTime.Add(IdleDeadline))
|
||||
}
|
|
@ -1,383 +0,0 @@
|
|||
package webhookconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/metrics"
|
||||
"github.com/kyverno/kyverno/pkg/tls"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
managedByLabel string = "webhook.kyverno.io/managed-by"
|
||||
)
|
||||
|
||||
var (
|
||||
noneOnDryRun = admissionregistrationv1.SideEffectClassNoneOnDryRun
|
||||
never = admissionregistrationv1.NeverReinvocationPolicy
|
||||
ifNeeded = admissionregistrationv1.IfNeededReinvocationPolicy
|
||||
policyRule = admissionregistrationv1.Rule{
|
||||
Resources: []string{"clusterpolicies/*", "policies/*"},
|
||||
APIGroups: []string{"kyverno.io"},
|
||||
APIVersions: []string{"v1", "v2beta1"},
|
||||
}
|
||||
verifyRule = admissionregistrationv1.Rule{
|
||||
Resources: []string{"leases"},
|
||||
APIGroups: []string{"coordination.k8s.io"},
|
||||
APIVersions: []string{"v1"},
|
||||
}
|
||||
vertifyObjectSelector = &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app.kubernetes.io/name": kyvernov1.ValueKyvernoApp,
|
||||
},
|
||||
}
|
||||
update = []admissionregistrationv1.OperationType{admissionregistrationv1.Update}
|
||||
createUpdate = []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update}
|
||||
all = []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete, admissionregistrationv1.Connect}
|
||||
)
|
||||
|
||||
func (wrc *Register) readCaData() []byte {
|
||||
logger := wrc.log.WithName("readCaData")
|
||||
var caData []byte
|
||||
var err error
|
||||
recorder := metrics.NamespacedClientQueryRecorder(wrc.metricsConfig, config.KyvernoNamespace(), "Secret", metrics.KubeClient)
|
||||
secretsClient := metrics.ObjectClient[*corev1.Secret](recorder, wrc.kubeClient.CoreV1().Secrets(config.KyvernoNamespace()))
|
||||
// Check if ca is defined in the secret tls-ca
|
||||
// assume the key and signed cert have been defined in secret tls.kyverno
|
||||
if caData, err = tls.ReadRootCASecret(secretsClient); err == nil {
|
||||
logger.V(4).Info("read CA from secret")
|
||||
return caData
|
||||
}
|
||||
|
||||
logger.V(4).Info("failed to read CA from kubeconfig")
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHealthyPodsIP(pods []corev1.Pod) []string {
|
||||
var ips []string
|
||||
for _, pod := range pods {
|
||||
if pod.Status.Phase == "Running" {
|
||||
ips = append(ips, pod.Status.PodIP)
|
||||
}
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
func (wrc *Register) GetKubePolicyClusterRoleName() (*rbacv1.ClusterRole, error) {
|
||||
selector := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app.kubernetes.io/name": kyvernov1.ValueKyvernoApp,
|
||||
},
|
||||
}
|
||||
clusterRoles, err := wrc.kubeClient.RbacV1().ClusterRoles().List(context.TODO(), metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(selector)})
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientList, metrics.KubeClient, "ClusterRole", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, cr := range clusterRoles.Items {
|
||||
if strings.HasSuffix(cr.GetName(), "webhook") {
|
||||
return &cr, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("failed to get cluster role with suffix webhook")
|
||||
}
|
||||
|
||||
// GetKubePolicyDeployment gets Kyverno deployment using the resource cache
|
||||
// it does not initialize any client call
|
||||
func (wrc *Register) GetKubePolicyDeployment() (*appsv1.Deployment, error) {
|
||||
deploy, err := wrc.kDeplLister.Deployments(config.KyvernoNamespace()).Get(config.KyvernoDeploymentName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deploy, nil
|
||||
}
|
||||
|
||||
func (wrc *Register) constructOwner() metav1.OwnerReference {
|
||||
logger := wrc.log
|
||||
kubeClusterRoleName, err := wrc.GetKubePolicyClusterRoleName()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to get cluster role")
|
||||
return metav1.OwnerReference{}
|
||||
}
|
||||
return metav1.OwnerReference{
|
||||
APIVersion: config.ClusterRoleAPIVersion,
|
||||
Kind: config.ClusterRoleKind,
|
||||
Name: kubeClusterRoleName.GetName(),
|
||||
UID: kubeClusterRoleName.GetUID(),
|
||||
}
|
||||
}
|
||||
|
||||
// webhook utils
|
||||
|
||||
func generateRules(rule admissionregistrationv1.Rule, operationTypes []admissionregistrationv1.OperationType) []admissionregistrationv1.RuleWithOperations {
|
||||
if !reflect.DeepEqual(rule, admissionregistrationv1.Rule{}) {
|
||||
return []admissionregistrationv1.RuleWithOperations{{Operations: operationTypes, Rule: rule}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateDebugMutatingWebhook(name, url string, caData []byte, timeoutSeconds int32, rule admissionregistrationv1.Rule, operationTypes []admissionregistrationv1.OperationType, failurePolicy admissionregistrationv1.FailurePolicyType) admissionregistrationv1.MutatingWebhook {
|
||||
return admissionregistrationv1.MutatingWebhook{
|
||||
ReinvocationPolicy: &never,
|
||||
Name: name,
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: &url,
|
||||
CABundle: caData,
|
||||
},
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
TimeoutSeconds: &timeoutSeconds,
|
||||
FailurePolicy: &failurePolicy,
|
||||
Rules: generateRules(rule, operationTypes),
|
||||
}
|
||||
}
|
||||
|
||||
func generateDebugValidatingWebhook(name, url string, caData []byte, timeoutSeconds int32, rule admissionregistrationv1.Rule, operationTypes []admissionregistrationv1.OperationType, failurePolicy admissionregistrationv1.FailurePolicyType) admissionregistrationv1.ValidatingWebhook {
|
||||
return admissionregistrationv1.ValidatingWebhook{
|
||||
Name: name,
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: &url,
|
||||
CABundle: caData,
|
||||
},
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
TimeoutSeconds: &timeoutSeconds,
|
||||
FailurePolicy: &failurePolicy,
|
||||
Rules: generateRules(rule, operationTypes),
|
||||
}
|
||||
}
|
||||
|
||||
func generateMutatingWebhook(name, servicePath string, caData []byte, timeoutSeconds int32, rule admissionregistrationv1.Rule, operationTypes []admissionregistrationv1.OperationType, failurePolicy admissionregistrationv1.FailurePolicyType) admissionregistrationv1.MutatingWebhook {
|
||||
return admissionregistrationv1.MutatingWebhook{
|
||||
ReinvocationPolicy: &ifNeeded,
|
||||
Name: name,
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
Service: &admissionregistrationv1.ServiceReference{
|
||||
Namespace: config.KyvernoNamespace(),
|
||||
Name: config.KyvernoServiceName(),
|
||||
Path: &servicePath,
|
||||
},
|
||||
CABundle: caData,
|
||||
},
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
TimeoutSeconds: &timeoutSeconds,
|
||||
FailurePolicy: &failurePolicy,
|
||||
Rules: generateRules(rule, operationTypes),
|
||||
}
|
||||
}
|
||||
|
||||
func generateValidatingWebhook(name, servicePath string, caData []byte, timeoutSeconds int32, rule admissionregistrationv1.Rule, operationTypes []admissionregistrationv1.OperationType, failurePolicy admissionregistrationv1.FailurePolicyType) admissionregistrationv1.ValidatingWebhook {
|
||||
return admissionregistrationv1.ValidatingWebhook{
|
||||
Name: name,
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
Service: &admissionregistrationv1.ServiceReference{
|
||||
Namespace: config.KyvernoNamespace(),
|
||||
Name: config.KyvernoServiceName(),
|
||||
Path: &servicePath,
|
||||
},
|
||||
CABundle: caData,
|
||||
},
|
||||
SideEffects: &noneOnDryRun,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
TimeoutSeconds: &timeoutSeconds,
|
||||
FailurePolicy: &failurePolicy,
|
||||
Rules: generateRules(rule, operationTypes),
|
||||
}
|
||||
}
|
||||
|
||||
func generateObjectMeta(name string, owner ...metav1.OwnerReference) metav1.ObjectMeta {
|
||||
return metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: map[string]string{
|
||||
managedByLabel: kyvernov1.ValueKyvernoApp,
|
||||
},
|
||||
OwnerReferences: owner,
|
||||
}
|
||||
}
|
||||
|
||||
// policy webhook configuration utils
|
||||
|
||||
func getPolicyMutatingWebhookConfigName(serverIP string) string {
|
||||
if serverIP != "" {
|
||||
return config.PolicyMutatingWebhookConfigurationDebugName
|
||||
}
|
||||
return config.PolicyMutatingWebhookConfigurationName
|
||||
}
|
||||
|
||||
func getPolicyValidatingWebhookConfigName(serverIP string) string {
|
||||
if serverIP != "" {
|
||||
return config.PolicyValidatingWebhookConfigurationDebugName
|
||||
}
|
||||
return config.PolicyValidatingWebhookConfigurationName
|
||||
}
|
||||
|
||||
func constructPolicyValidatingWebhookConfig(caData []byte, timeoutSeconds int32, owner metav1.OwnerReference) *admissionregistrationv1.ValidatingWebhookConfiguration {
|
||||
name, path := config.PolicyValidatingWebhookName, config.PolicyValidatingWebhookServicePath
|
||||
return &admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.PolicyValidatingWebhookConfigurationName, owner),
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{
|
||||
generateValidatingWebhook(name, path, caData, timeoutSeconds, policyRule, createUpdate, admissionregistrationv1.Ignore),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func constructDebugPolicyValidatingWebhookConfig(serverIP string, caData []byte, timeoutSeconds int32, owner metav1.OwnerReference) *admissionregistrationv1.ValidatingWebhookConfiguration {
|
||||
name, url := config.PolicyValidatingWebhookName, fmt.Sprintf("https://%s%s", serverIP, config.PolicyValidatingWebhookServicePath)
|
||||
return &admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.PolicyValidatingWebhookConfigurationDebugName, owner),
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{
|
||||
generateDebugValidatingWebhook(name, url, caData, timeoutSeconds, policyRule, createUpdate, admissionregistrationv1.Ignore),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func constructPolicyMutatingWebhookConfig(caData []byte, timeoutSeconds int32, owner metav1.OwnerReference) *admissionregistrationv1.MutatingWebhookConfiguration {
|
||||
name, path := config.PolicyMutatingWebhookName, config.PolicyMutatingWebhookServicePath
|
||||
return &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.PolicyMutatingWebhookConfigurationName, owner),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{
|
||||
generateMutatingWebhook(name, path, caData, timeoutSeconds, policyRule, createUpdate, admissionregistrationv1.Ignore),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func constructDebugPolicyMutatingWebhookConfig(serverIP string, caData []byte, timeoutSeconds int32, owner metav1.OwnerReference) *admissionregistrationv1.MutatingWebhookConfiguration {
|
||||
name, url := config.PolicyMutatingWebhookName, fmt.Sprintf("https://%s%s", serverIP, config.PolicyMutatingWebhookServicePath)
|
||||
return &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.PolicyMutatingWebhookConfigurationDebugName, owner),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{
|
||||
generateDebugMutatingWebhook(name, url, caData, timeoutSeconds, policyRule, createUpdate, admissionregistrationv1.Ignore),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// resource webhook configuration utils
|
||||
|
||||
func getResourceMutatingWebhookConfigName(serverIP string) string {
|
||||
if serverIP != "" {
|
||||
return config.MutatingWebhookConfigurationDebugName
|
||||
}
|
||||
return config.MutatingWebhookConfigurationName
|
||||
}
|
||||
|
||||
func getResourceValidatingWebhookConfigName(serverIP string) string {
|
||||
if serverIP != "" {
|
||||
return config.ValidatingWebhookConfigurationDebugName
|
||||
}
|
||||
return config.ValidatingWebhookConfigurationName
|
||||
}
|
||||
|
||||
func defaultResourceWebhookRule(autoUpdate bool) admissionregistrationv1.Rule {
|
||||
if autoUpdate {
|
||||
return admissionregistrationv1.Rule{}
|
||||
}
|
||||
return admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"*"},
|
||||
APIVersions: []string{"*"},
|
||||
Resources: []string{"*/*"},
|
||||
}
|
||||
}
|
||||
|
||||
func constructDefaultDebugMutatingWebhookConfig(serverIP string, caData []byte, timeoutSeconds int32, autoUpdate bool, owner metav1.OwnerReference) *admissionregistrationv1.MutatingWebhookConfiguration {
|
||||
name, baseUrl := config.MutatingWebhookName, fmt.Sprintf("https://%s%s", serverIP, config.MutatingWebhookServicePath)
|
||||
url := fmt.Sprintf("%s/ignore", baseUrl)
|
||||
webhook := &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.MutatingWebhookConfigurationDebugName, owner),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{
|
||||
generateDebugMutatingWebhook(name+"-ignore", url, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), createUpdate, admissionregistrationv1.Ignore),
|
||||
},
|
||||
}
|
||||
if autoUpdate {
|
||||
url := fmt.Sprintf("%s/fail", baseUrl)
|
||||
webhook.Webhooks = append(webhook.Webhooks, generateDebugMutatingWebhook(name+"-fail", url, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), createUpdate, admissionregistrationv1.Fail))
|
||||
}
|
||||
return webhook
|
||||
}
|
||||
|
||||
func constructDefaultMutatingWebhookConfig(caData []byte, timeoutSeconds int32, autoUpdate bool, owner metav1.OwnerReference) *admissionregistrationv1.MutatingWebhookConfiguration {
|
||||
name, basePath := config.MutatingWebhookName, config.MutatingWebhookServicePath
|
||||
path := fmt.Sprintf("%s/ignore", basePath)
|
||||
webhook := &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.MutatingWebhookConfigurationName, owner),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{
|
||||
generateMutatingWebhook(name+"-ignore", path, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), createUpdate, admissionregistrationv1.Ignore),
|
||||
},
|
||||
}
|
||||
if autoUpdate {
|
||||
path := fmt.Sprintf("%s/fail", basePath)
|
||||
webhook.Webhooks = append(webhook.Webhooks, generateMutatingWebhook(name+"-fail", path, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), createUpdate, admissionregistrationv1.Fail))
|
||||
}
|
||||
return webhook
|
||||
}
|
||||
|
||||
func constructDefaultDebugValidatingWebhookConfig(serverIP string, caData []byte, timeoutSeconds int32, autoUpdate bool, owner metav1.OwnerReference) *admissionregistrationv1.ValidatingWebhookConfiguration {
|
||||
name, baseUrl := config.ValidatingWebhookName, fmt.Sprintf("https://%s%s", serverIP, config.ValidatingWebhookServicePath)
|
||||
url := fmt.Sprintf("%s/ignore", baseUrl)
|
||||
webhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.ValidatingWebhookConfigurationDebugName, owner),
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{
|
||||
generateDebugValidatingWebhook(name+"-ignore", url, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), all, admissionregistrationv1.Ignore),
|
||||
},
|
||||
}
|
||||
if autoUpdate {
|
||||
url := fmt.Sprintf("%s/fail", baseUrl)
|
||||
webhook.Webhooks = append(webhook.Webhooks, generateDebugValidatingWebhook(name+"-fail", url, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), all, admissionregistrationv1.Fail))
|
||||
}
|
||||
return webhook
|
||||
}
|
||||
|
||||
func constructDefaultValidatingWebhookConfig(caData []byte, timeoutSeconds int32, autoUpdate bool, owner metav1.OwnerReference) *admissionregistrationv1.ValidatingWebhookConfiguration {
|
||||
name, basePath := config.ValidatingWebhookName, config.ValidatingWebhookServicePath
|
||||
path := fmt.Sprintf("%s/ignore", basePath)
|
||||
webhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.ValidatingWebhookConfigurationName, owner),
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{
|
||||
generateValidatingWebhook(name+"-ignore", path, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), all, admissionregistrationv1.Ignore),
|
||||
},
|
||||
}
|
||||
if autoUpdate {
|
||||
path := fmt.Sprintf("%s/fail", basePath)
|
||||
webhook.Webhooks = append(webhook.Webhooks, generateValidatingWebhook(name+"-fail", path, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), all, admissionregistrationv1.Fail))
|
||||
}
|
||||
return webhook
|
||||
}
|
||||
|
||||
// verify webhook configuration utils
|
||||
|
||||
func getVerifyMutatingWebhookConfigName(serverIP string) string {
|
||||
if serverIP != "" {
|
||||
return config.VerifyMutatingWebhookConfigurationDebugName
|
||||
}
|
||||
return config.VerifyMutatingWebhookConfigurationName
|
||||
}
|
||||
|
||||
func constructVerifyMutatingWebhookConfig(caData []byte, timeoutSeconds int32, owner metav1.OwnerReference) *admissionregistrationv1.MutatingWebhookConfiguration {
|
||||
name, path := config.VerifyMutatingWebhookName, config.VerifyMutatingWebhookServicePath
|
||||
webhook := generateMutatingWebhook(name, path, caData, timeoutSeconds, verifyRule, update, admissionregistrationv1.Ignore)
|
||||
webhook.ObjectSelector = vertifyObjectSelector
|
||||
return &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.VerifyMutatingWebhookConfigurationName, owner),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{webhook},
|
||||
}
|
||||
}
|
||||
|
||||
func constructDebugVerifyMutatingWebhookConfig(serverIP string, caData []byte, timeoutSeconds int32, owner metav1.OwnerReference) *admissionregistrationv1.MutatingWebhookConfiguration {
|
||||
name, url := config.VerifyMutatingWebhookName, fmt.Sprintf("https://%s%s", serverIP, config.VerifyMutatingWebhookServicePath)
|
||||
webhook := generateDebugMutatingWebhook(name, url, caData, timeoutSeconds, verifyRule, update, admissionregistrationv1.Ignore)
|
||||
webhook.ObjectSelector = vertifyObjectSelector
|
||||
return &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: generateObjectMeta(config.VerifyMutatingWebhookConfigurationDebugName, owner),
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{webhook},
|
||||
}
|
||||
}
|
|
@ -1,681 +0,0 @@
|
|||
package webhookconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/autogen"
|
||||
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||
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/metrics"
|
||||
"github.com/kyverno/kyverno/pkg/toggle"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
"github.com/pkg/errors"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
admissionregistrationv1informers "k8s.io/client-go/informers/admissionregistration/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
admissionregistrationv1listers "k8s.io/client-go/listers/admissionregistration/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
)
|
||||
|
||||
var DefaultWebhookTimeout int32 = 10
|
||||
|
||||
// webhookConfigManager manges the webhook configuration dynamically
|
||||
// it is NOT multi-thread safe
|
||||
type webhookConfigManager struct {
|
||||
// clients
|
||||
discoveryClient dclient.IDiscovery
|
||||
kubeClient kubernetes.Interface
|
||||
kyvernoClient versioned.Interface
|
||||
|
||||
// informers
|
||||
pInformer kyvernov1informers.ClusterPolicyInformer
|
||||
npInformer kyvernov1informers.PolicyInformer
|
||||
mutateInformer admissionregistrationv1informers.MutatingWebhookConfigurationInformer
|
||||
validateInformer admissionregistrationv1informers.ValidatingWebhookConfigurationInformer
|
||||
|
||||
// listers
|
||||
pLister kyvernov1listers.ClusterPolicyLister
|
||||
npLister kyvernov1listers.PolicyLister
|
||||
mutateLister admissionregistrationv1listers.MutatingWebhookConfigurationLister
|
||||
validateLister admissionregistrationv1listers.ValidatingWebhookConfigurationLister
|
||||
|
||||
// queue
|
||||
queue workqueue.RateLimitingInterface
|
||||
|
||||
metricsConfig metrics.MetricsConfigManager
|
||||
|
||||
// serverIP used to get the name of debug webhooks
|
||||
serverIP string
|
||||
autoUpdateWebhooks bool
|
||||
|
||||
// wildcardPolicy indicates the number of policies that matches all kinds (*) defined
|
||||
wildcardPolicy int64
|
||||
|
||||
createDefaultWebhook chan<- string
|
||||
|
||||
stopCh <-chan struct{}
|
||||
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
type manage interface {
|
||||
start()
|
||||
}
|
||||
|
||||
func newWebhookConfigManager(
|
||||
ctx context.Context,
|
||||
discoveryClient dclient.IDiscovery,
|
||||
kubeClient kubernetes.Interface,
|
||||
kyvernoClient versioned.Interface,
|
||||
pInformer kyvernov1informers.ClusterPolicyInformer,
|
||||
npInformer kyvernov1informers.PolicyInformer,
|
||||
mwcInformer admissionregistrationv1informers.MutatingWebhookConfigurationInformer,
|
||||
vwcInformer admissionregistrationv1informers.ValidatingWebhookConfigurationInformer,
|
||||
metricsConfig metrics.MetricsConfigManager,
|
||||
serverIP string,
|
||||
autoUpdateWebhooks bool,
|
||||
createDefaultWebhook chan<- string,
|
||||
log logr.Logger,
|
||||
) manage {
|
||||
m := &webhookConfigManager{
|
||||
discoveryClient: discoveryClient,
|
||||
kyvernoClient: kyvernoClient,
|
||||
kubeClient: kubeClient,
|
||||
pInformer: pInformer,
|
||||
npInformer: npInformer,
|
||||
mutateInformer: mwcInformer,
|
||||
validateInformer: vwcInformer,
|
||||
pLister: pInformer.Lister(),
|
||||
npLister: npInformer.Lister(),
|
||||
mutateLister: mwcInformer.Lister(),
|
||||
validateLister: vwcInformer.Lister(),
|
||||
metricsConfig: metricsConfig,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "configmanager"),
|
||||
wildcardPolicy: 0,
|
||||
serverIP: serverIP,
|
||||
autoUpdateWebhooks: autoUpdateWebhooks,
|
||||
createDefaultWebhook: createDefaultWebhook,
|
||||
stopCh: ctx.Done(),
|
||||
log: log,
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) handleErr(err error, key interface{}) {
|
||||
logger := m.log
|
||||
if err == nil {
|
||||
m.queue.Forget(key)
|
||||
return
|
||||
}
|
||||
if m.queue.NumRequeues(key) < 3 {
|
||||
logger.Error(err, "failed to sync policy", "key", key)
|
||||
m.queue.AddRateLimited(key)
|
||||
return
|
||||
}
|
||||
utilruntime.HandleError(err)
|
||||
logger.V(2).Info("dropping policy out of queue", "key", key)
|
||||
m.queue.Forget(key)
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) addClusterPolicy(obj interface{}) {
|
||||
p := obj.(*kyvernov1.ClusterPolicy)
|
||||
if hasWildcard(&p.Spec) {
|
||||
atomic.AddInt64(&m.wildcardPolicy, int64(1))
|
||||
}
|
||||
m.enqueue(p)
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) updateClusterPolicy(old, cur interface{}) {
|
||||
oldP, curP := old.(*kyvernov1.ClusterPolicy), cur.(*kyvernov1.ClusterPolicy)
|
||||
if reflect.DeepEqual(oldP.Spec, curP.Spec) {
|
||||
return
|
||||
}
|
||||
if hasWildcard(&oldP.Spec) && !hasWildcard(&curP.Spec) {
|
||||
atomic.AddInt64(&m.wildcardPolicy, ^int64(0))
|
||||
} else if !hasWildcard(&oldP.Spec) && hasWildcard(&curP.Spec) {
|
||||
atomic.AddInt64(&m.wildcardPolicy, int64(1))
|
||||
}
|
||||
m.enqueue(curP)
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) deleteClusterPolicy(obj interface{}) {
|
||||
p, ok := kubeutils.GetObjectWithTombstone(obj).(*kyvernov1.ClusterPolicy)
|
||||
if !ok {
|
||||
// utilruntime.HandleError(fmt.Errorf("error decoding object tombstone, invalid type"))
|
||||
m.log.V(2).Info("Failed to get deleted object", "obj", obj)
|
||||
return
|
||||
}
|
||||
if hasWildcard(&p.Spec) {
|
||||
atomic.AddInt64(&m.wildcardPolicy, ^int64(0))
|
||||
}
|
||||
m.enqueue(p)
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) addPolicy(obj interface{}) {
|
||||
p := obj.(*kyvernov1.Policy)
|
||||
if hasWildcard(&p.Spec) {
|
||||
atomic.AddInt64(&m.wildcardPolicy, int64(1))
|
||||
}
|
||||
m.enqueue(p)
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) updatePolicy(old, cur interface{}) {
|
||||
oldP, curP := old.(*kyvernov1.Policy), cur.(*kyvernov1.Policy)
|
||||
if reflect.DeepEqual(oldP.Spec, curP.Spec) {
|
||||
return
|
||||
}
|
||||
if hasWildcard(&oldP.Spec) && !hasWildcard(&curP.Spec) {
|
||||
atomic.AddInt64(&m.wildcardPolicy, ^int64(0))
|
||||
} else if !hasWildcard(&oldP.Spec) && hasWildcard(&curP.Spec) {
|
||||
atomic.AddInt64(&m.wildcardPolicy, int64(1))
|
||||
}
|
||||
m.enqueue(curP)
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) deletePolicy(obj interface{}) {
|
||||
p, ok := kubeutils.GetObjectWithTombstone(obj).(*kyvernov1.Policy)
|
||||
if !ok {
|
||||
// utilruntime.HandleError(fmt.Errorf("error decoding object tombstone, invalid type"))
|
||||
m.log.V(2).Info("Failed to get deleted object", "obj", obj)
|
||||
return
|
||||
}
|
||||
if hasWildcard(&p.Spec) {
|
||||
atomic.AddInt64(&m.wildcardPolicy, ^int64(0))
|
||||
}
|
||||
m.enqueue(p)
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) deleteMutatingWebhook(obj interface{}) {
|
||||
m.log.WithName("deleteMutatingWebhook").Info("resource webhook configuration was deleted, recreating...")
|
||||
webhook, ok := kubeutils.GetObjectWithTombstone(obj).(*admissionregistrationv1.MutatingWebhookConfiguration)
|
||||
if !ok {
|
||||
m.log.V(2).Info("Failed to get deleted object", "obj", obj)
|
||||
return
|
||||
}
|
||||
if webhook.GetName() == config.MutatingWebhookConfigurationName {
|
||||
m.enqueueAllPolicies()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) deleteValidatingWebhook(obj interface{}) {
|
||||
m.log.WithName("deleteMutatingWebhook").Info("resource webhook configuration was deleted, recreating...")
|
||||
webhook, ok := kubeutils.GetObjectWithTombstone(obj).(*admissionregistrationv1.ValidatingWebhookConfiguration)
|
||||
if !ok {
|
||||
m.log.V(2).Info("Failed to get deleted object", "obj", obj)
|
||||
return
|
||||
}
|
||||
if webhook.GetName() == config.ValidatingWebhookConfigurationName {
|
||||
m.enqueueAllPolicies()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) enqueueAllPolicies() {
|
||||
logger := m.log.WithName("enqueueAllPolicies")
|
||||
policies, err := m.listAllPolicies()
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to list policies")
|
||||
}
|
||||
for _, policy := range policies {
|
||||
m.enqueue(policy)
|
||||
logger.V(4).Info("added policy to the queue", "namespace", policy.GetNamespace(), "name", policy.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) enqueue(policy interface{}) {
|
||||
logger := m.log
|
||||
key, err := cache.MetaNamespaceKeyFunc(policy)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to enqueue policy")
|
||||
return
|
||||
}
|
||||
m.queue.Add(key)
|
||||
}
|
||||
|
||||
// start is a blocking call to configure webhook
|
||||
func (m *webhookConfigManager) start() {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer m.queue.ShutDown()
|
||||
|
||||
m.log.V(2).Info("starting")
|
||||
defer m.log.V(2).Info("shutting down")
|
||||
|
||||
m.pInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: m.addClusterPolicy,
|
||||
UpdateFunc: m.updateClusterPolicy,
|
||||
DeleteFunc: m.deleteClusterPolicy,
|
||||
})
|
||||
|
||||
m.npInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: m.addPolicy,
|
||||
UpdateFunc: m.updatePolicy,
|
||||
DeleteFunc: m.deletePolicy,
|
||||
})
|
||||
|
||||
m.mutateInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
DeleteFunc: m.deleteMutatingWebhook,
|
||||
})
|
||||
|
||||
m.validateInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
DeleteFunc: m.deleteValidatingWebhook,
|
||||
})
|
||||
|
||||
for m.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) processNextWorkItem() bool {
|
||||
key, quit := m.queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer m.queue.Done(key)
|
||||
err := m.sync(key.(string))
|
||||
m.handleErr(err, key)
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) sync(key string) error {
|
||||
logger := m.log.WithName("sync")
|
||||
startTime := time.Now()
|
||||
logger.V(4).Info("started syncing policy", "key", key, "startTime", startTime)
|
||||
defer func() {
|
||||
logger.V(4).Info("finished syncing policy", "key", key, "processingTime", time.Since(startTime).String())
|
||||
}()
|
||||
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
logger.Info("invalid resource key", "key", key)
|
||||
return nil
|
||||
}
|
||||
return m.reconcileWebhook(namespace, name)
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) reconcileWebhook(namespace, name string) error {
|
||||
logger := m.log.WithName("reconcileWebhook").WithValues("namespace", namespace, "policy", name)
|
||||
|
||||
_, err := m.getPolicy(namespace, name)
|
||||
isDeleted := apierrors.IsNotFound(err)
|
||||
if err != nil && !isDeleted {
|
||||
return errors.Wrapf(err, "unable to get policy object %s/%s", namespace, name)
|
||||
}
|
||||
|
||||
ready := true
|
||||
var updateErr error
|
||||
// build webhook only if auto-update is enabled, otherwise directly update status to ready
|
||||
if m.autoUpdateWebhooks {
|
||||
webhooks, err := m.buildWebhooks(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.updateWebhookConfig(webhooks); err != nil {
|
||||
ready = false
|
||||
updateErr = errors.Wrapf(err, "failed to update webhook configurations for policy")
|
||||
}
|
||||
|
||||
// DELETION of the policy
|
||||
if isDeleted {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.updateStatus(namespace, name, ready); err != nil {
|
||||
return errors.Wrapf(err, "failed to update policy status %s/%s", namespace, name)
|
||||
}
|
||||
|
||||
if ready {
|
||||
logger.Info("policy is ready to serve admission requests")
|
||||
}
|
||||
return updateErr
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) getPolicy(namespace, name string) (kyvernov1.PolicyInterface, error) {
|
||||
if namespace == "" {
|
||||
return m.pLister.Get(name)
|
||||
} else {
|
||||
return m.npLister.Policies(namespace).Get(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) listAllPolicies() ([]kyvernov1.PolicyInterface, error) {
|
||||
policies := []kyvernov1.PolicyInterface{}
|
||||
polList, err := m.npLister.Policies(metav1.NamespaceAll).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to list Policy")
|
||||
}
|
||||
for _, p := range polList {
|
||||
policies = append(policies, p)
|
||||
}
|
||||
cpolList, err := m.pLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to list ClusterPolicy")
|
||||
}
|
||||
for _, p := range cpolList {
|
||||
policies = append(policies, p)
|
||||
}
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) buildWebhooks(namespace string) (res []*webhook, err error) {
|
||||
mutateIgnore := newWebhook(kindMutating, DefaultWebhookTimeout, kyvernov1.Ignore)
|
||||
mutateFail := newWebhook(kindMutating, DefaultWebhookTimeout, kyvernov1.Fail)
|
||||
validateIgnore := newWebhook(kindValidating, DefaultWebhookTimeout, kyvernov1.Ignore)
|
||||
validateFail := newWebhook(kindValidating, DefaultWebhookTimeout, kyvernov1.Fail)
|
||||
|
||||
if atomic.LoadInt64(&m.wildcardPolicy) != 0 {
|
||||
for _, w := range []*webhook{mutateIgnore, mutateFail, validateIgnore, validateFail} {
|
||||
setWildcardConfig(w)
|
||||
}
|
||||
|
||||
m.log.V(4).WithName("buildWebhooks").Info("warning: found wildcard policy, setting webhook configurations to accept admission requests of all kinds")
|
||||
return append(res, mutateIgnore, mutateFail, validateIgnore, validateFail), nil
|
||||
}
|
||||
|
||||
policies, err := m.listAllPolicies()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to list current policies")
|
||||
}
|
||||
|
||||
for _, p := range policies {
|
||||
spec := p.GetSpec()
|
||||
if spec.HasValidate() || spec.HasGenerate() || spec.HasMutate() || spec.HasImagesValidationChecks() || spec.HasYAMLSignatureVerify() {
|
||||
if spec.GetFailurePolicy() == kyvernov1.Ignore {
|
||||
m.mergeWebhook(validateIgnore, p, true)
|
||||
} else {
|
||||
m.mergeWebhook(validateFail, p, true)
|
||||
}
|
||||
}
|
||||
|
||||
if spec.HasMutate() || spec.HasVerifyImages() {
|
||||
if spec.GetFailurePolicy() == kyvernov1.Ignore {
|
||||
m.mergeWebhook(mutateIgnore, p, false)
|
||||
} else {
|
||||
m.mergeWebhook(mutateFail, p, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, mutateIgnore, mutateFail, validateIgnore, validateFail)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) updateWebhookConfig(webhooks []*webhook) error {
|
||||
logger := m.log.WithName("updateWebhookConfig")
|
||||
|
||||
webhooksMap := map[string]*webhook{}
|
||||
for _, w := range webhooks {
|
||||
webhooksMap[webhookKey(w.kind, string(w.failurePolicy))] = w
|
||||
}
|
||||
|
||||
var errs []string
|
||||
if err := m.updateMutatingWebhookConfiguration(getResourceMutatingWebhookConfigName(m.serverIP), webhooksMap); err != nil {
|
||||
logger.V(4).Info("failed to update mutatingwebhookconfigurations", "error", err.Error())
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
if err := m.updateValidatingWebhookConfiguration(getResourceValidatingWebhookConfigName(m.serverIP), webhooksMap); err != nil {
|
||||
logger.V(4).Info("failed to update validatingwebhookconfigurations", "error", err.Error())
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) updateMutatingWebhookConfiguration(webhookName string, webhooksMap map[string]*webhook) error {
|
||||
logger := m.log.WithName("updateMutatingWebhookConfiduration").WithValues("name", webhookName)
|
||||
resourceWebhook, err := m.mutateLister.Get(webhookName)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return errors.Wrapf(err, "unable to get %s/%s", kindMutating, webhookName)
|
||||
} else if apierrors.IsNotFound(err) {
|
||||
m.createDefaultWebhook <- kindMutating
|
||||
return err
|
||||
}
|
||||
for i := range resourceWebhook.Webhooks {
|
||||
newWebhook := webhooksMap[webhookKey(kindMutating, string(*resourceWebhook.Webhooks[i].FailurePolicy))]
|
||||
if newWebhook == nil || newWebhook.isEmpty() {
|
||||
resourceWebhook.Webhooks[i].Rules = []admissionregistrationv1.RuleWithOperations{}
|
||||
} else {
|
||||
resourceWebhook.Webhooks[i].TimeoutSeconds = &newWebhook.maxWebhookTimeout
|
||||
resourceWebhook.Webhooks[i].Rules = []admissionregistrationv1.RuleWithOperations{
|
||||
newWebhook.buildRuleWithOperations(admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete),
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := m.kubeClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Update(context.TODO(), resourceWebhook, metav1.UpdateOptions{}); err != nil {
|
||||
m.metricsConfig.RecordClientQueries(metrics.ClientUpdate, metrics.KubeClient, kindMutating, "")
|
||||
return errors.Wrapf(err, "unable to update: %s", resourceWebhook.GetName())
|
||||
}
|
||||
logger.V(4).Info("successfully updated the webhook configuration")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) updateValidatingWebhookConfiguration(webhookName string, webhooksMap map[string]*webhook) error {
|
||||
logger := m.log.WithName("updateMutatingWebhookConfiduration").WithValues("name", webhookName)
|
||||
resourceWebhook, err := m.validateLister.Get(webhookName)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return errors.Wrapf(err, "unable to get %s/%s", kindValidating, webhookName)
|
||||
} else if apierrors.IsNotFound(err) {
|
||||
m.createDefaultWebhook <- kindValidating
|
||||
return err
|
||||
}
|
||||
for i := range resourceWebhook.Webhooks {
|
||||
newWebhook := webhooksMap[webhookKey(kindValidating, string(*resourceWebhook.Webhooks[i].FailurePolicy))]
|
||||
if newWebhook == nil || newWebhook.isEmpty() {
|
||||
resourceWebhook.Webhooks[i].Rules = []admissionregistrationv1.RuleWithOperations{}
|
||||
} else {
|
||||
resourceWebhook.Webhooks[i].TimeoutSeconds = &newWebhook.maxWebhookTimeout
|
||||
resourceWebhook.Webhooks[i].Rules = []admissionregistrationv1.RuleWithOperations{
|
||||
newWebhook.buildRuleWithOperations(admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete, admissionregistrationv1.Connect),
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := m.kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Update(context.TODO(), resourceWebhook, metav1.UpdateOptions{}); err != nil {
|
||||
m.metricsConfig.RecordClientQueries(metrics.ClientUpdate, metrics.KubeClient, kindValidating, "")
|
||||
return errors.Wrapf(err, "unable to update: %s", resourceWebhook.GetName())
|
||||
}
|
||||
logger.V(4).Info("successfully updated the webhook configuration")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *webhookConfigManager) updateStatus(namespace, name string, ready bool) error {
|
||||
update := func(meta *metav1.ObjectMeta, p kyvernov1.PolicyInterface, status *kyvernov1.PolicyStatus) bool {
|
||||
copy := status.DeepCopy()
|
||||
status.SetReady(ready)
|
||||
if toggle.AutogenInternals.Enabled() {
|
||||
var rules []kyvernov1.Rule
|
||||
for _, rule := range autogen.ComputeRules(p) {
|
||||
if strings.HasPrefix(rule.Name, "autogen-") {
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
}
|
||||
status.Autogen.Rules = rules
|
||||
} else {
|
||||
status.Autogen.Rules = nil
|
||||
}
|
||||
return !reflect.DeepEqual(status, copy)
|
||||
}
|
||||
if namespace == "" {
|
||||
p, err := m.pLister.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if update(&p.ObjectMeta, p, &p.Status) {
|
||||
if _, err := m.kyvernoClient.KyvernoV1().ClusterPolicies().UpdateStatus(context.TODO(), p, metav1.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p, err := m.npLister.Policies(namespace).Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if update(&p.ObjectMeta, p, &p.Status) {
|
||||
if _, err := m.kyvernoClient.KyvernoV1().Policies(namespace).UpdateStatus(context.TODO(), p, metav1.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// webhook is the instance that aggregates the GVK of existing policies
|
||||
// based on kind, failurePolicy and webhookTimeout
|
||||
type webhook struct {
|
||||
kind string
|
||||
maxWebhookTimeout int32
|
||||
failurePolicy kyvernov1.FailurePolicyType
|
||||
groups sets.String
|
||||
versions sets.String
|
||||
resources sets.String
|
||||
}
|
||||
|
||||
func (wh *webhook) buildRuleWithOperations(ops ...admissionregistrationv1.OperationType) admissionregistrationv1.RuleWithOperations {
|
||||
return admissionregistrationv1.RuleWithOperations{
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: wh.groups.List(),
|
||||
APIVersions: wh.versions.List(),
|
||||
Resources: wh.resources.List(),
|
||||
},
|
||||
Operations: ops,
|
||||
}
|
||||
}
|
||||
|
||||
func (wh *webhook) isEmpty() bool {
|
||||
return wh.groups.Len() == 0 || wh.versions.Len() == 0 || wh.resources.Len() == 0
|
||||
}
|
||||
|
||||
// mergeWebhook merges the matching kinds of the policy to webhook.rule
|
||||
func (m *webhookConfigManager) mergeWebhook(dst *webhook, policy kyvernov1.PolicyInterface, updateValidate bool) {
|
||||
matchedGVK := make([]string, 0)
|
||||
for _, rule := range autogen.ComputeRules(policy) {
|
||||
// matching kinds in generate policies need to be added to both webhook
|
||||
if rule.HasGenerate() {
|
||||
matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...)
|
||||
matchedGVK = append(matchedGVK, rule.Generation.ResourceSpec.Kind)
|
||||
continue
|
||||
}
|
||||
|
||||
if (updateValidate && rule.HasValidate() || rule.HasImagesValidationChecks()) ||
|
||||
(updateValidate && rule.HasMutate() && rule.IsMutateExisting()) ||
|
||||
(!updateValidate && rule.HasMutate()) && !rule.IsMutateExisting() ||
|
||||
(!updateValidate && rule.HasVerifyImages()) || (!updateValidate && rule.HasYAMLSignatureVerify()) {
|
||||
matchedGVK = append(matchedGVK, rule.MatchResources.GetKinds()...)
|
||||
}
|
||||
}
|
||||
|
||||
gvkMap := make(map[string]int)
|
||||
gvrList := make([]schema.GroupVersionResource, 0)
|
||||
for _, gvk := range matchedGVK {
|
||||
if _, ok := gvkMap[gvk]; !ok {
|
||||
gvkMap[gvk] = 1
|
||||
|
||||
// note: webhook stores GVR in its rules while policy stores GVK in its rules definition
|
||||
gv, k := kubeutils.GetKindFromGVK(gvk)
|
||||
switch k {
|
||||
case "Binding":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/binding"})
|
||||
case "NodeProxyOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nodes/proxy"})
|
||||
case "PodAttachOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/attach"})
|
||||
case "PodExecOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/exec"})
|
||||
case "PodPortForwardOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/portforward"})
|
||||
case "PodProxyOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods/proxy"})
|
||||
case "ServiceProxyOptions":
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services/proxy"})
|
||||
default:
|
||||
_, gvr, err := m.discoveryClient.FindResource(gv, k)
|
||||
if err != nil {
|
||||
m.log.Error(err, "unable to convert GVK to GVR", "GVK", gvk)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(gvk, "*") {
|
||||
group := kubeutils.GetGroupFromGVK(gvk)
|
||||
gvrList = append(gvrList, schema.GroupVersionResource{Group: group, Version: "*", Resource: gvr.Resource})
|
||||
} else {
|
||||
m.log.V(4).Info("configuring webhook", "GVK", gvk, "GVR", gvr)
|
||||
gvrList = append(gvrList, gvr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, gvr := range gvrList {
|
||||
dst.groups.Insert(gvr.Group)
|
||||
if gvr.Version == "*" {
|
||||
dst.versions = sets.NewString()
|
||||
dst.versions.Insert(gvr.Version)
|
||||
} else if !dst.versions.Has("*") {
|
||||
dst.versions.Insert(gvr.Version)
|
||||
}
|
||||
dst.resources.Insert(gvr.Resource)
|
||||
}
|
||||
|
||||
if dst.resources.Has("pods") {
|
||||
dst.resources.Insert("pods/ephemeralcontainers")
|
||||
}
|
||||
if dst.resources.Has("services") {
|
||||
dst.resources.Insert("services/status")
|
||||
}
|
||||
|
||||
spec := policy.GetSpec()
|
||||
if spec.WebhookTimeoutSeconds != nil {
|
||||
if dst.maxWebhookTimeout < *spec.WebhookTimeoutSeconds {
|
||||
dst.maxWebhookTimeout = *spec.WebhookTimeoutSeconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newWebhook(kind string, timeout int32, failurePolicy kyvernov1.FailurePolicyType) *webhook {
|
||||
return &webhook{
|
||||
kind: kind,
|
||||
maxWebhookTimeout: timeout,
|
||||
failurePolicy: failurePolicy,
|
||||
groups: sets.NewString(),
|
||||
versions: sets.NewString(),
|
||||
resources: sets.NewString(),
|
||||
}
|
||||
}
|
||||
|
||||
func webhookKey(webhookKind, failurePolicy string) string {
|
||||
return strings.Join([]string{webhookKind, failurePolicy}, "/")
|
||||
}
|
||||
|
||||
func hasWildcard(spec *kyvernov1.Spec) bool {
|
||||
for _, rule := range spec.Rules {
|
||||
if kinds := rule.MatchResources.GetKinds(); utils.ContainsString(kinds, "*") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setWildcardConfig(w *webhook) {
|
||||
w.groups = sets.NewString("*")
|
||||
w.versions = sets.NewString("*")
|
||||
w.resources = sets.NewString("*/*")
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package webhookconfig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func Test_webhook_isEmpty(t *testing.T) {
|
||||
empty := newWebhook(kindMutating, DefaultWebhookTimeout, kyverno.Ignore)
|
||||
assert.Equal(t, empty.isEmpty(), true)
|
||||
notEmpty := newWebhook(kindMutating, DefaultWebhookTimeout, kyverno.Ignore)
|
||||
setWildcardConfig(notEmpty)
|
||||
assert.Equal(t, notEmpty.isEmpty(), false)
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
package webhookconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/event"
|
||||
"github.com/kyverno/kyverno/pkg/tls"
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
|
||||
)
|
||||
|
||||
// maxRetryCount defines the max deadline count
|
||||
const (
|
||||
tickerInterval time.Duration = 30 * time.Second
|
||||
idleCheckInterval time.Duration = 60 * time.Second
|
||||
idleDeadline time.Duration = idleCheckInterval * 5
|
||||
)
|
||||
|
||||
// Monitor stores the last webhook request time and monitors registered webhooks.
|
||||
//
|
||||
// If a webhook is not received in the idleCheckInterval the monitor triggers a
|
||||
// change in the Kyverno deployment to force a webhook request. If no requests
|
||||
// are received after idleDeadline the webhooks are deleted and re-registered.
|
||||
//
|
||||
// Each instance has an in-memory flag lastSeenRequestTime, recording the last
|
||||
// received admission timestamp by the current instance. And the latest timestamp
|
||||
// (latestTimestamp) is recorded in the annotation of the Kyverno deployment,
|
||||
// this annotation could be updated by any instance. If the duration from
|
||||
// latestTimestamp is longer than idleCheckInterval, the monitor triggers an
|
||||
// annotation update; otherwise lastSeenRequestTime is updated to latestTimestamp.
|
||||
//
|
||||
// Webhook configurations are checked every tickerInterval across all instances.
|
||||
// Currently the check only queries for the expected resource name, and does
|
||||
// not compare other details like the webhook settings.
|
||||
type Monitor struct {
|
||||
// leaseClient is used to manage Kyverno lease
|
||||
leaseClient coordinationv1.LeaseInterface
|
||||
|
||||
// lastSeenRequestTime records the timestamp
|
||||
// of the latest received admission request
|
||||
lastSeenRequestTime time.Time
|
||||
mu sync.RWMutex
|
||||
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
// NewMonitor returns a new instance of webhook monitor
|
||||
func NewMonitor(kubeClient kubernetes.Interface, log logr.Logger) (*Monitor, error) {
|
||||
monitor := &Monitor{
|
||||
leaseClient: kubeClient.CoordinationV1().Leases(config.KyvernoNamespace()),
|
||||
lastSeenRequestTime: time.Now(),
|
||||
log: log,
|
||||
}
|
||||
|
||||
return monitor, nil
|
||||
}
|
||||
|
||||
// Time returns the last request time
|
||||
func (t *Monitor) Time() time.Time {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
return t.lastSeenRequestTime
|
||||
}
|
||||
|
||||
// SetTime updates the last request time
|
||||
func (t *Monitor) SetTime(tm time.Time) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.lastSeenRequestTime = tm
|
||||
}
|
||||
|
||||
// Run runs the checker and verify the resource update
|
||||
func (t *Monitor) Run(ctx context.Context, register *Register, certRenewer *tls.CertRenewer, eventGen event.Interface) {
|
||||
logger := t.log.WithName("webhookMonitor")
|
||||
|
||||
logger.V(3).Info("starting webhook monitor", "interval", idleCheckInterval.String())
|
||||
status := newStatusControl(t.leaseClient, eventGen, logger.WithName("WebhookStatusControl"))
|
||||
|
||||
ticker := time.NewTicker(tickerInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
createDefaultWebhook := register.createDefaultWebhook
|
||||
for {
|
||||
select {
|
||||
case webhookKind := <-createDefaultWebhook:
|
||||
logger.Info("received recreation request for resource webhook")
|
||||
if webhookKind == kindMutating {
|
||||
err := register.createResourceMutatingWebhookConfiguration(register.readCaData())
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to create default MutatingWebhookConfiguration for resources, the webhook will be reconciled", "interval", tickerInterval)
|
||||
}
|
||||
} else if webhookKind == kindValidating {
|
||||
err := register.createResourceValidatingWebhookConfiguration(register.readCaData())
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to create default ValidatingWebhookConfiguration for resources, the webhook will be reconciled", "interval", tickerInterval)
|
||||
}
|
||||
}
|
||||
|
||||
case <-ticker.C:
|
||||
|
||||
err := registerWebhookIfNotPresent(register, t.log.WithName("registerWebhookIfNotPresent"))
|
||||
if err != nil {
|
||||
t.log.Error(err, "")
|
||||
}
|
||||
|
||||
// update namespaceSelector every 30 seconds
|
||||
go func() {
|
||||
if register.autoUpdateWebhooks {
|
||||
select {
|
||||
case register.UpdateWebhookChan <- true:
|
||||
logger.V(4).Info("updating webhook configurations for namespaceSelector with latest kyverno ConfigMap")
|
||||
default:
|
||||
logger.V(4).Info("skipped sending update webhook signal as the channel was blocking")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
timeDiff := time.Since(t.Time())
|
||||
lastRequestTimeFromAnn := lastRequestTimeFromAnnotation(t.leaseClient, t.log.WithName("lastRequestTimeFromAnnotation"))
|
||||
if lastRequestTimeFromAnn == nil {
|
||||
if err := status.UpdateLastRequestTimestmap(t.Time()); err != nil {
|
||||
logger.Error(err, "failed to annotate deployment for lastRequestTime")
|
||||
} else {
|
||||
logger.Info("initialized lastRequestTimestamp", "time", t.Time())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case timeDiff > idleDeadline:
|
||||
err := fmt.Errorf("webhook hasn't received requests in %v, updating Kyverno to verify webhook status", idleDeadline.String())
|
||||
logger.Error(err, "webhook check failed", "time", t.Time(), "lastRequestTimestamp", lastRequestTimeFromAnn)
|
||||
|
||||
// update deployment to renew lastSeenRequestTime
|
||||
if err := status.failure(); err != nil {
|
||||
logger.Error(err, "failed to annotate deployment webhook status to failure")
|
||||
|
||||
if err := register.Register(); err != nil {
|
||||
logger.Error(err, "Failed to register webhooks")
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
|
||||
case timeDiff > 2*idleCheckInterval:
|
||||
if skipWebhookCheck(register, logger.WithName("skipWebhookCheck")) {
|
||||
logger.Info("skip validating webhook status, Kyverno is in rolling update")
|
||||
continue
|
||||
}
|
||||
|
||||
if t.Time().Before(*lastRequestTimeFromAnn) {
|
||||
t.SetTime(*lastRequestTimeFromAnn)
|
||||
logger.V(3).Info("updated in-memory timestamp", "time", lastRequestTimeFromAnn)
|
||||
}
|
||||
}
|
||||
|
||||
idleT := time.Since(*lastRequestTimeFromAnn)
|
||||
if idleT > idleCheckInterval {
|
||||
if t.Time().After(*lastRequestTimeFromAnn) {
|
||||
logger.V(3).Info("updating annotation lastRequestTimestamp with the latest in-memory timestamp", "time", t.Time(), "lastRequestTimestamp", lastRequestTimeFromAnn)
|
||||
if err := status.UpdateLastRequestTimestmap(t.Time()); err != nil {
|
||||
logger.Error(err, "failed to update lastRequestTimestamp annotation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the status was false before then we update it to true
|
||||
// send request to update the Kyverno deployment
|
||||
if err := status.success(); err != nil {
|
||||
logger.Error(err, "failed to annotate deployment webhook status to success")
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
// handler termination signal
|
||||
logger.V(2).Info("stopping webhook monitor")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerWebhookIfNotPresent(register *Register, logger logr.Logger) error {
|
||||
if skipWebhookCheck(register, logger.WithName("skipWebhookCheck")) {
|
||||
logger.Info("skip validating webhook status, Kyverno is in rolling update")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := register.Check(); err != nil {
|
||||
logger.Error(err, "missing webhooks")
|
||||
|
||||
if err := register.Register(); err != nil {
|
||||
return errors.Wrap(err, "failed to register webhooks")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lastRequestTimeFromAnnotation(leaseClient coordinationv1.LeaseInterface, logger logr.Logger) *time.Time {
|
||||
lease, err := leaseClient.Get(context.TODO(), "kyverno", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
logger.Info("Lease 'kyverno' not found. Starting clean-up...")
|
||||
}
|
||||
|
||||
timeStamp := lease.GetAnnotations()
|
||||
if timeStamp == nil {
|
||||
logger.Info("timestamp not set in the annotation, setting")
|
||||
return nil
|
||||
}
|
||||
|
||||
annTime, err := time.Parse(time.RFC3339, timeStamp[annLastRequestTime])
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to parse timestamp annotation", "timeStamp", timeStamp[annLastRequestTime])
|
||||
return nil
|
||||
}
|
||||
|
||||
return &annTime
|
||||
}
|
||||
|
||||
// skipWebhookCheck returns true if Kyverno is in rolling update
|
||||
func skipWebhookCheck(register *Register, logger logr.Logger) bool {
|
||||
deploy, err := register.GetKubePolicyDeployment()
|
||||
if err != nil {
|
||||
logger.Info("unable to get Kyverno deployment", "reason", err.Error())
|
||||
return false
|
||||
}
|
||||
return tls.IsKyvernoInRollingUpdate(deploy)
|
||||
}
|
|
@ -1,653 +0,0 @@
|
|||
package webhookconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
|
||||
kyvernov1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/metrics"
|
||||
tlsutils "github.com/kyverno/kyverno/pkg/tls"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
errorsapi "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
admissionregistrationv1informers "k8s.io/client-go/informers/admissionregistration/v1"
|
||||
appsv1informers "k8s.io/client-go/informers/apps/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
admissionregistrationv1listers "k8s.io/client-go/listers/admissionregistration/v1"
|
||||
appsv1listers "k8s.io/client-go/listers/apps/v1"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
kindMutating string = "MutatingWebhookConfiguration"
|
||||
kindValidating string = "ValidatingWebhookConfiguration"
|
||||
)
|
||||
|
||||
// Register manages webhook registration. There are five webhooks:
|
||||
// 1. Policy Validation
|
||||
// 2. Policy Mutation
|
||||
// 3. Resource Validation
|
||||
// 4. Resource Mutation
|
||||
// 5. Webhook Status Mutation
|
||||
type Register struct {
|
||||
// clients
|
||||
kubeClient kubernetes.Interface
|
||||
kyvernoClient versioned.Interface
|
||||
clientConfig *rest.Config
|
||||
|
||||
// listers
|
||||
mwcLister admissionregistrationv1listers.MutatingWebhookConfigurationLister
|
||||
vwcLister admissionregistrationv1listers.ValidatingWebhookConfigurationLister
|
||||
kDeplLister appsv1listers.DeploymentLister
|
||||
|
||||
metricsConfig metrics.MetricsConfigManager
|
||||
|
||||
// channels
|
||||
stopCh <-chan struct{}
|
||||
UpdateWebhookChan chan bool
|
||||
createDefaultWebhook chan string
|
||||
|
||||
serverIP string // when running outside a cluster
|
||||
timeoutSeconds int32
|
||||
log logr.Logger
|
||||
autoUpdateWebhooks bool
|
||||
|
||||
// manage implements methods to manage webhook configurations
|
||||
manage
|
||||
}
|
||||
|
||||
// NewRegister creates new Register instance
|
||||
func NewRegister(
|
||||
ctx context.Context,
|
||||
clientConfig *rest.Config,
|
||||
client dclient.Interface,
|
||||
kubeClient kubernetes.Interface,
|
||||
kyvernoClient versioned.Interface,
|
||||
mwcInformer admissionregistrationv1informers.MutatingWebhookConfigurationInformer,
|
||||
vwcInformer admissionregistrationv1informers.ValidatingWebhookConfigurationInformer,
|
||||
kDeplInformer appsv1informers.DeploymentInformer,
|
||||
pInformer kyvernov1informers.ClusterPolicyInformer,
|
||||
npInformer kyvernov1informers.PolicyInformer,
|
||||
metricsConfig metrics.MetricsConfigManager,
|
||||
serverIP string,
|
||||
webhookTimeout int32,
|
||||
autoUpdateWebhooks bool,
|
||||
log logr.Logger,
|
||||
) *Register {
|
||||
register := &Register{
|
||||
clientConfig: clientConfig,
|
||||
kubeClient: kubeClient,
|
||||
kyvernoClient: kyvernoClient,
|
||||
mwcLister: mwcInformer.Lister(),
|
||||
vwcLister: vwcInformer.Lister(),
|
||||
kDeplLister: kDeplInformer.Lister(),
|
||||
metricsConfig: metricsConfig,
|
||||
UpdateWebhookChan: make(chan bool),
|
||||
createDefaultWebhook: make(chan string),
|
||||
stopCh: ctx.Done(),
|
||||
serverIP: serverIP,
|
||||
timeoutSeconds: webhookTimeout,
|
||||
log: log.WithName("Register"),
|
||||
autoUpdateWebhooks: autoUpdateWebhooks,
|
||||
}
|
||||
|
||||
register.manage = newWebhookConfigManager(
|
||||
ctx,
|
||||
client.Discovery(),
|
||||
kubeClient,
|
||||
kyvernoClient,
|
||||
pInformer,
|
||||
npInformer,
|
||||
mwcInformer,
|
||||
vwcInformer,
|
||||
metricsConfig,
|
||||
serverIP,
|
||||
register.autoUpdateWebhooks,
|
||||
register.createDefaultWebhook,
|
||||
log.WithName("WebhookConfigManager"),
|
||||
)
|
||||
|
||||
return register
|
||||
}
|
||||
|
||||
// Register clean up the old webhooks and re-creates admission webhooks configs on cluster
|
||||
func (wrc *Register) Register() error {
|
||||
logger := wrc.log
|
||||
if wrc.serverIP != "" {
|
||||
logger.Info("Registering webhook", "url", fmt.Sprintf("https://%s", wrc.serverIP))
|
||||
} else {
|
||||
if err := wrc.checkEndpoint(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
caData := wrc.readCaData()
|
||||
if caData == nil {
|
||||
return errors.New("Unable to extract CA data from configuration")
|
||||
}
|
||||
var errors []string
|
||||
if err := wrc.createVerifyMutatingWebhookConfiguration(caData); err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
if err := wrc.createPolicyValidatingWebhookConfiguration(caData); err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
if err := wrc.createPolicyMutatingWebhookConfiguration(caData); err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
if err := wrc.createResourceValidatingWebhookConfiguration(caData); err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
if err := wrc.createResourceMutatingWebhookConfiguration(caData); err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("%s", strings.Join(errors, ","))
|
||||
}
|
||||
go wrc.manage.start()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check returns an error if any of the webhooks are not configured
|
||||
func (wrc *Register) Check() error {
|
||||
if _, err := wrc.mwcLister.Get(getVerifyMutatingWebhookConfigName(wrc.serverIP)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := wrc.mwcLister.Get(getResourceMutatingWebhookConfigName(wrc.serverIP)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := wrc.vwcLister.Get(getResourceValidatingWebhookConfigName(wrc.serverIP)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := wrc.mwcLister.Get(getPolicyMutatingWebhookConfigName(wrc.serverIP)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := wrc.vwcLister.Get(getPolicyValidatingWebhookConfigName(wrc.serverIP)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes all webhook configurations
|
||||
func (wrc *Register) Remove(cleanupKyvernoResource bool, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
// delete Lease object to let init container do the cleanup
|
||||
if err := wrc.kubeClient.CoordinationV1().Leases(config.KyvernoNamespace()).Delete(context.TODO(), "kyvernopre-lock", metav1.DeleteOptions{}); err != nil && errorsapi.IsNotFound(err) {
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientDelete, metrics.KubeClient, "Lease", config.KyvernoNamespace())
|
||||
wrc.log.WithName("cleanup").Error(err, "failed to clean up Lease lock")
|
||||
}
|
||||
if cleanupKyvernoResource {
|
||||
wrc.removeWebhookConfigurations()
|
||||
}
|
||||
}
|
||||
|
||||
func (wrc *Register) ResetPolicyStatus(kyvernoInTermination bool, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
if !kyvernoInTermination {
|
||||
return
|
||||
}
|
||||
|
||||
logger := wrc.log.WithName("ResetPolicyStatus")
|
||||
cpols, err := wrc.kyvernoClient.KyvernoV1().ClusterPolicies().List(context.TODO(), metav1.ListOptions{})
|
||||
if err == nil {
|
||||
for _, item := range cpols.Items {
|
||||
cpol := item
|
||||
cpol.Status.SetReady(false)
|
||||
if _, err := wrc.kyvernoClient.KyvernoV1().ClusterPolicies().UpdateStatus(context.TODO(), &cpol, metav1.UpdateOptions{}); err != nil {
|
||||
logger.Error(err, "failed to set ClusterPolicy status READY=false", "name", cpol.GetName())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Error(err, "failed to list clusterpolicies")
|
||||
}
|
||||
|
||||
pols, err := wrc.kyvernoClient.KyvernoV1().Policies(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
|
||||
if err == nil {
|
||||
for _, item := range pols.Items {
|
||||
pol := item
|
||||
pol.Status.SetReady(false)
|
||||
if _, err := wrc.kyvernoClient.KyvernoV1().Policies(pol.GetNamespace()).UpdateStatus(context.TODO(), &pol, metav1.UpdateOptions{}); err != nil {
|
||||
logger.Error(err, "failed to set Policy status READY=false", "namespace", pol.GetNamespace(), "name", pol.GetName())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Error(err, "failed to list namespaced policies")
|
||||
}
|
||||
}
|
||||
|
||||
// GetWebhookTimeOut returns the value of webhook timeout
|
||||
func (wrc *Register) GetWebhookTimeOut() time.Duration {
|
||||
return time.Duration(wrc.timeoutSeconds)
|
||||
}
|
||||
|
||||
// UpdateWebhookConfigurations updates resource webhook configurations dynamically
|
||||
// based on the UPDATEs of Kyverno ConfigMap defined in INIT_CONFIG env
|
||||
//
|
||||
// it currently updates namespaceSelector only, can be extend to update other fields
|
||||
// +deprecated
|
||||
func (wrc *Register) UpdateWebhookConfigurations(configHandler config.Configuration) {
|
||||
logger := wrc.log.WithName("UpdateWebhookConfigurations")
|
||||
for {
|
||||
<-wrc.UpdateWebhookChan
|
||||
logger.V(4).Info("received the signal to update webhook configurations")
|
||||
|
||||
retry := false
|
||||
if wrc.serverIP != "" {
|
||||
deploy, err := wrc.GetKubePolicyDeployment()
|
||||
if err != nil {
|
||||
retry = true
|
||||
} else {
|
||||
if tlsutils.IsKyvernoInRollingUpdate(deploy) {
|
||||
retry = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !retry {
|
||||
webhookCfgs := configHandler.GetWebhooks()
|
||||
webhookCfg := config.WebhookConfig{}
|
||||
if len(webhookCfgs) > 0 {
|
||||
webhookCfg = webhookCfgs[0]
|
||||
}
|
||||
|
||||
if err := wrc.updateResourceMutatingWebhookConfiguration(webhookCfg); err != nil {
|
||||
logger.Error(err, "unable to update mutatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
|
||||
retry = true
|
||||
}
|
||||
|
||||
if err := wrc.updateResourceValidatingWebhookConfiguration(webhookCfg); err != nil {
|
||||
logger.Error(err, "unable to update validatingWebhookConfigurations", "name", getResourceValidatingWebhookConfigName(wrc.serverIP))
|
||||
retry = true
|
||||
}
|
||||
}
|
||||
|
||||
if retry {
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
select {
|
||||
case wrc.UpdateWebhookChan <- true:
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wrc *Register) ValidateWebhookConfigurations(namespace, name string) error {
|
||||
logger := wrc.log.WithName("ValidateWebhookConfigurations")
|
||||
cm, err := wrc.kubeClient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientGet, metrics.KubeClient, "ConfigMap", namespace)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to fetch ConfigMap", "namespace", namespace, "name", name)
|
||||
return nil
|
||||
}
|
||||
webhooks, ok := cm.Data["webhooks"]
|
||||
if !ok {
|
||||
logger.V(4).Info("webhook configurations not defined")
|
||||
return nil
|
||||
}
|
||||
webhookCfgs := make([]config.WebhookConfig, 0, 10)
|
||||
return json.Unmarshal([]byte(webhooks), &webhookCfgs)
|
||||
}
|
||||
|
||||
func (wrc *Register) createMutatingWebhookConfiguration(config *admissionregistrationv1.MutatingWebhookConfiguration) error {
|
||||
logger := wrc.log.WithValues("kind", kindMutating, "name", config.Name)
|
||||
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientCreate, metrics.KubeClient, kindMutating, "")
|
||||
if _, err := wrc.kubeClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), config, metav1.CreateOptions{}); err != nil {
|
||||
if errorsapi.IsAlreadyExists(err) {
|
||||
logger.V(6).Info("resource mutating webhook configuration already exists", "name", config.Name)
|
||||
return wrc.updateMutatingWebhookConfiguration(config)
|
||||
}
|
||||
logger.Error(err, "failed to create resource mutating webhook configuration", "name", config.Name)
|
||||
return err
|
||||
}
|
||||
logger.Info("created webhook")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wrc *Register) createValidatingWebhookConfiguration(config *admissionregistrationv1.ValidatingWebhookConfiguration) error {
|
||||
logger := wrc.log.WithValues("kind", kindValidating, "name", config.Name)
|
||||
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientCreate, metrics.KubeClient, kindValidating, "")
|
||||
if _, err := wrc.kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), config, metav1.CreateOptions{}); err != nil {
|
||||
if errorsapi.IsAlreadyExists(err) {
|
||||
logger.V(6).Info("resource validating webhook configuration already exists", "name", config.Name)
|
||||
return wrc.updateValidatingWebhookConfiguration(config)
|
||||
}
|
||||
logger.Error(err, "failed to create resource validating webhook configuration", "name", config.Name)
|
||||
return err
|
||||
}
|
||||
logger.Info("created webhook")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wrc *Register) createResourceMutatingWebhookConfiguration(caData []byte) error {
|
||||
owner := wrc.constructOwner()
|
||||
var config *admissionregistrationv1.MutatingWebhookConfiguration
|
||||
if wrc.serverIP != "" {
|
||||
config = constructDefaultDebugMutatingWebhookConfig(wrc.serverIP, caData, wrc.timeoutSeconds, wrc.autoUpdateWebhooks, owner)
|
||||
} else {
|
||||
config = constructDefaultMutatingWebhookConfig(caData, wrc.timeoutSeconds, wrc.autoUpdateWebhooks, owner)
|
||||
}
|
||||
return wrc.createMutatingWebhookConfiguration(config)
|
||||
}
|
||||
|
||||
func (wrc *Register) createResourceValidatingWebhookConfiguration(caData []byte) error {
|
||||
owner := wrc.constructOwner()
|
||||
var config *admissionregistrationv1.ValidatingWebhookConfiguration
|
||||
if wrc.serverIP != "" {
|
||||
config = constructDefaultDebugValidatingWebhookConfig(wrc.serverIP, caData, wrc.timeoutSeconds, wrc.autoUpdateWebhooks, owner)
|
||||
} else {
|
||||
config = constructDefaultValidatingWebhookConfig(caData, wrc.timeoutSeconds, wrc.autoUpdateWebhooks, owner)
|
||||
}
|
||||
return wrc.createValidatingWebhookConfiguration(config)
|
||||
}
|
||||
|
||||
func (wrc *Register) createPolicyValidatingWebhookConfiguration(caData []byte) error {
|
||||
owner := wrc.constructOwner()
|
||||
var config *admissionregistrationv1.ValidatingWebhookConfiguration
|
||||
if wrc.serverIP != "" {
|
||||
config = constructDebugPolicyValidatingWebhookConfig(wrc.serverIP, caData, wrc.timeoutSeconds, owner)
|
||||
} else {
|
||||
config = constructPolicyValidatingWebhookConfig(caData, wrc.timeoutSeconds, owner)
|
||||
}
|
||||
return wrc.createValidatingWebhookConfiguration(config)
|
||||
}
|
||||
|
||||
func (wrc *Register) createPolicyMutatingWebhookConfiguration(caData []byte) error {
|
||||
owner := wrc.constructOwner()
|
||||
var config *admissionregistrationv1.MutatingWebhookConfiguration
|
||||
if wrc.serverIP != "" {
|
||||
config = constructDebugPolicyMutatingWebhookConfig(wrc.serverIP, caData, wrc.timeoutSeconds, owner)
|
||||
} else {
|
||||
config = constructPolicyMutatingWebhookConfig(caData, wrc.timeoutSeconds, owner)
|
||||
}
|
||||
return wrc.createMutatingWebhookConfiguration(config)
|
||||
}
|
||||
|
||||
func (wrc *Register) createVerifyMutatingWebhookConfiguration(caData []byte) error {
|
||||
owner := wrc.constructOwner()
|
||||
var config *admissionregistrationv1.MutatingWebhookConfiguration
|
||||
if wrc.serverIP != "" {
|
||||
config = constructDebugVerifyMutatingWebhookConfig(wrc.serverIP, caData, wrc.timeoutSeconds, owner)
|
||||
} else {
|
||||
config = constructVerifyMutatingWebhookConfig(caData, wrc.timeoutSeconds, owner)
|
||||
}
|
||||
return wrc.createMutatingWebhookConfiguration(config)
|
||||
}
|
||||
|
||||
func (wrc *Register) checkEndpoint() error {
|
||||
endpoint, err := wrc.kubeClient.CoreV1().Endpoints(config.KyvernoNamespace()).Get(context.TODO(), config.KyvernoServiceName(), metav1.GetOptions{})
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientGet, metrics.KubeClient, "EndPoint", config.KyvernoNamespace())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get endpoint %s/%s: %v", config.KyvernoNamespace(), config.KyvernoServiceName(), err)
|
||||
}
|
||||
deploy, err := wrc.GetKubePolicyDeployment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tlsutils.IsKyvernoInRollingUpdate(deploy) {
|
||||
return errors.New("kyverno is in rolling update, please update the timeout by setting the webhookRegistrationTimeout flag")
|
||||
}
|
||||
selector := &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app.kubernetes.io/name": kyvernov1.ValueKyvernoApp,
|
||||
},
|
||||
}
|
||||
pods, err := wrc.kubeClient.CoreV1().Pods(config.KyvernoNamespace()).List(context.TODO(), metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(selector)})
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientList, metrics.KubeClient, "Pod", config.KyvernoNamespace())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list Kyverno Pod: %v", err)
|
||||
}
|
||||
ips := getHealthyPodsIP(pods.Items)
|
||||
if len(ips) == 0 {
|
||||
return fmt.Errorf("pod is not assigned to any node yet")
|
||||
}
|
||||
for _, subset := range endpoint.Subsets {
|
||||
if len(subset.Addresses) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, addr := range subset.Addresses {
|
||||
if utils.ContainsString(ips, addr.IP) {
|
||||
wrc.log.V(2).Info("Endpoint ready", "ns", config.KyvernoNamespace(), "name", config.KyvernoServiceName())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("endpoint not ready")
|
||||
wrc.log.V(3).Info(err.Error(), "ns", config.KyvernoNamespace(), "name", config.KyvernoServiceName())
|
||||
return err
|
||||
}
|
||||
|
||||
func (wrc *Register) updateResourceValidatingWebhookConfiguration(webhookCfg config.WebhookConfig) error {
|
||||
resource, err := wrc.vwcLister.Get(getResourceValidatingWebhookConfigName(wrc.serverIP))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to get validatingWebhookConfigurations")
|
||||
}
|
||||
copy := resource.DeepCopy()
|
||||
for i := range copy.Webhooks {
|
||||
copy.Webhooks[i].ObjectSelector = webhookCfg.ObjectSelector
|
||||
copy.Webhooks[i].NamespaceSelector = webhookCfg.NamespaceSelector
|
||||
}
|
||||
if reflect.DeepEqual(resource.Webhooks, copy.Webhooks) {
|
||||
wrc.log.V(4).Info("namespaceSelector unchanged, skip updating validatingWebhookConfigurations")
|
||||
return nil
|
||||
}
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientUpdate, metrics.KubeClient, kindValidating, "")
|
||||
if _, err := wrc.kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Update(context.TODO(), copy, metav1.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
wrc.log.V(3).Info("successfully updated validatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wrc *Register) updateResourceMutatingWebhookConfiguration(webhookCfg config.WebhookConfig) error {
|
||||
resource, err := wrc.mwcLister.Get(getResourceMutatingWebhookConfigName(wrc.serverIP))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to get mutatingWebhookConfigurations")
|
||||
}
|
||||
copy := resource.DeepCopy()
|
||||
for i := range copy.Webhooks {
|
||||
copy.Webhooks[i].ObjectSelector = webhookCfg.ObjectSelector
|
||||
copy.Webhooks[i].NamespaceSelector = webhookCfg.NamespaceSelector
|
||||
}
|
||||
if reflect.DeepEqual(resource.Webhooks, copy.Webhooks) {
|
||||
wrc.log.V(4).Info("namespaceSelector unchanged, skip updating mutatingWebhookConfigurations")
|
||||
return nil
|
||||
}
|
||||
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientUpdate, metrics.KubeClient, kindMutating, "")
|
||||
if _, err := wrc.kubeClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Update(context.TODO(), copy, metav1.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
wrc.log.V(3).Info("successfully updated mutatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateMutatingWebhookConfiguration updates an existing MutatingWebhookConfiguration with the rules provided by
|
||||
// the targetConfig. If the targetConfig doesn't provide any rules, the existing rules will be preserved.
|
||||
func (wrc *Register) updateMutatingWebhookConfiguration(targetConfig *admissionregistrationv1.MutatingWebhookConfiguration) error {
|
||||
// Fetch the existing webhook.
|
||||
currentConfiguration, err := wrc.mwcLister.Get(targetConfig.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get %s %s: %v", kindMutating, targetConfig.Name, err)
|
||||
}
|
||||
// Create a map of the target webhooks.
|
||||
targetWebhooksMap := make(map[string]admissionregistrationv1.MutatingWebhook)
|
||||
for _, w := range targetConfig.Webhooks {
|
||||
targetWebhooksMap[w.Name] = w
|
||||
}
|
||||
// Update the webhooks.
|
||||
newWebhooks := make([]admissionregistrationv1.MutatingWebhook, 0)
|
||||
for _, w := range currentConfiguration.Webhooks {
|
||||
target, exist := targetWebhooksMap[w.Name]
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
delete(targetWebhooksMap, w.Name)
|
||||
// Update the webhook configuration
|
||||
w.ClientConfig.URL = target.ClientConfig.URL
|
||||
w.ClientConfig.Service = target.ClientConfig.Service
|
||||
w.ClientConfig.CABundle = target.ClientConfig.CABundle
|
||||
if target.Rules != nil {
|
||||
// If the target webhook has rule definitions override the current.
|
||||
w.Rules = target.Rules
|
||||
}
|
||||
newWebhooks = append(newWebhooks, w)
|
||||
}
|
||||
// Check if there are additional webhooks defined and add them.
|
||||
for _, w := range targetWebhooksMap {
|
||||
newWebhooks = append(newWebhooks, w)
|
||||
}
|
||||
// Update the current configuration.
|
||||
currentConfiguration.Webhooks = newWebhooks
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientUpdate, metrics.KubeClient, kindMutating, "")
|
||||
if _, err := wrc.kubeClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Update(context.TODO(), currentConfiguration, metav1.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
wrc.log.V(3).Info("successfully updated mutatingWebhookConfigurations", "name", targetConfig.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateValidatingWebhookConfiguration updates an existing ValidatingWebhookConfiguration with the rules provided by
|
||||
// the targetConfig. If the targetConfig doesn't provide any rules, the existing rules will be preserved.
|
||||
func (wrc *Register) updateValidatingWebhookConfiguration(targetConfig *admissionregistrationv1.ValidatingWebhookConfiguration) error {
|
||||
// Fetch the existing webhook.
|
||||
currentConfiguration, err := wrc.vwcLister.Get(targetConfig.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get %s %s: %v", kindValidating, targetConfig.Name, err)
|
||||
}
|
||||
// Create a map of the target webhooks.
|
||||
targetWebhooksMap := make(map[string]admissionregistrationv1.ValidatingWebhook)
|
||||
for _, w := range targetConfig.Webhooks {
|
||||
targetWebhooksMap[w.Name] = w
|
||||
}
|
||||
// Update the webhooks.
|
||||
newWebhooks := make([]admissionregistrationv1.ValidatingWebhook, 0)
|
||||
for _, w := range currentConfiguration.Webhooks {
|
||||
target, exist := targetWebhooksMap[w.Name]
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
delete(targetWebhooksMap, w.Name)
|
||||
// Update the webhook configuration
|
||||
w.ClientConfig.URL = target.ClientConfig.URL
|
||||
w.ClientConfig.Service = target.ClientConfig.Service
|
||||
w.ClientConfig.CABundle = target.ClientConfig.CABundle
|
||||
if target.Rules != nil {
|
||||
// If the target webhook has rule definitions override the current.
|
||||
w.Rules = target.Rules
|
||||
}
|
||||
newWebhooks = append(newWebhooks, w)
|
||||
}
|
||||
// Check if there are additional webhooks defined and add them.
|
||||
for _, w := range targetWebhooksMap {
|
||||
newWebhooks = append(newWebhooks, w)
|
||||
}
|
||||
// Update the current configuration.
|
||||
currentConfiguration.Webhooks = newWebhooks
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientUpdate, metrics.KubeClient, kindValidating, "")
|
||||
if _, err := wrc.kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Update(context.TODO(), currentConfiguration, metav1.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
wrc.log.V(3).Info("successfully updated validatingWebhookConfigurations", "name", targetConfig.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wrc *Register) ShouldCleanupKyvernoResource() bool {
|
||||
logger := wrc.log.WithName("cleanupKyvernoResource")
|
||||
deploy, err := wrc.kubeClient.AppsV1().Deployments(config.KyvernoNamespace()).Get(context.TODO(), config.KyvernoDeploymentName(), metav1.GetOptions{})
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientGet, metrics.KubeClient, "Deployment", config.KyvernoNamespace())
|
||||
if err != nil {
|
||||
if errorsapi.IsNotFound(err) {
|
||||
logger.Info("Kyverno deployment not found, cleanup Kyverno resources")
|
||||
return true
|
||||
}
|
||||
logger.Error(err, "failed to get deployment, not cleaning up kyverno resources")
|
||||
return false
|
||||
}
|
||||
if deploy.GetDeletionTimestamp() != nil {
|
||||
logger.Info("Kyverno is terminating, cleanup Kyverno resources")
|
||||
return true
|
||||
}
|
||||
if deploy.Spec.Replicas != nil && *deploy.Spec.Replicas == 0 {
|
||||
logger.Info("Kyverno is scaled to zero, cleanup Kyverno resources")
|
||||
return true
|
||||
}
|
||||
logger.Info("updating Kyverno Pod, won't clean up Kyverno resources")
|
||||
return false
|
||||
}
|
||||
|
||||
func (wrc *Register) removeWebhookConfigurations() {
|
||||
startTime := time.Now()
|
||||
wrc.log.V(3).Info("deleting all webhook configurations")
|
||||
defer wrc.log.V(4).Info("removed webhook configurations", "processingTime", time.Since(startTime).String())
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(5)
|
||||
go wrc.removeResourceMutatingWebhookConfiguration(&wg)
|
||||
go wrc.removeResourceValidatingWebhookConfiguration(&wg)
|
||||
go wrc.removePolicyMutatingWebhookConfiguration(&wg)
|
||||
go wrc.removePolicyValidatingWebhookConfiguration(&wg)
|
||||
go wrc.removeVerifyWebhookMutatingWebhookConfig(&wg)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (wrc *Register) removeResourceMutatingWebhookConfiguration(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
wrc.removeMutatingWebhookConfiguration(getResourceMutatingWebhookConfigName(wrc.serverIP))
|
||||
}
|
||||
|
||||
func (wrc *Register) removeResourceValidatingWebhookConfiguration(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
wrc.removeValidatingWebhookConfiguration(getResourceValidatingWebhookConfigName(wrc.serverIP))
|
||||
}
|
||||
|
||||
func (wrc *Register) removePolicyMutatingWebhookConfiguration(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
wrc.removeMutatingWebhookConfiguration(getPolicyMutatingWebhookConfigName(wrc.serverIP))
|
||||
}
|
||||
|
||||
func (wrc *Register) removePolicyValidatingWebhookConfiguration(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
wrc.removeValidatingWebhookConfiguration(getPolicyValidatingWebhookConfigName(wrc.serverIP))
|
||||
}
|
||||
|
||||
func (wrc *Register) removeVerifyWebhookMutatingWebhookConfig(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
wrc.removeMutatingWebhookConfiguration(getVerifyMutatingWebhookConfigName(wrc.serverIP))
|
||||
}
|
||||
|
||||
func (wrc *Register) removeMutatingWebhookConfiguration(name string) {
|
||||
logger := wrc.log.WithValues("kind", kindMutating, "name", name)
|
||||
if err := wrc.kubeClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil && !errorsapi.IsNotFound(err) {
|
||||
logger.Error(err, "failed to delete the mutating webhook configuration")
|
||||
} else {
|
||||
logger.Info("webhook configuration deleted")
|
||||
}
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientDelete, metrics.KubeClient, kindMutating, "")
|
||||
}
|
||||
|
||||
func (wrc *Register) removeValidatingWebhookConfiguration(name string) {
|
||||
logger := wrc.log.WithValues("kind", kindValidating, "name", name)
|
||||
if err := wrc.kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil && !errorsapi.IsNotFound(err) {
|
||||
logger.Error(err, "failed to delete the validating webhook configuration")
|
||||
} else {
|
||||
logger.Info("webhook configuration deleted")
|
||||
}
|
||||
wrc.metricsConfig.RecordClientQueries(metrics.ClientDelete, metrics.KubeClient, kindValidating, "")
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
package webhookconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/event"
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
leaseName string = "kyverno"
|
||||
annWebhookStatus string = "kyverno.io/webhookActive"
|
||||
annLastRequestTime string = "kyverno.io/last-request-time"
|
||||
)
|
||||
|
||||
// statusControl controls the webhook status
|
||||
type statusControl struct {
|
||||
eventGen event.Interface
|
||||
log logr.Logger
|
||||
leaseClient coordinationv1.LeaseInterface
|
||||
}
|
||||
|
||||
// success ...
|
||||
func (vc statusControl) success() error {
|
||||
return vc.setStatus("true")
|
||||
}
|
||||
|
||||
// failure ...
|
||||
func (vc statusControl) failure() error {
|
||||
return vc.setStatus("false")
|
||||
}
|
||||
|
||||
// NewStatusControl creates a new webhook status control
|
||||
func newStatusControl(leaseClient coordinationv1.LeaseInterface, eventGen event.Interface, log logr.Logger) *statusControl {
|
||||
return &statusControl{
|
||||
eventGen: eventGen,
|
||||
log: log,
|
||||
leaseClient: leaseClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (vc statusControl) setStatus(status string) error {
|
||||
logger := vc.log.WithValues("name", leaseName, "namespace", config.KyvernoNamespace())
|
||||
var ann map[string]string
|
||||
var err error
|
||||
|
||||
lease, err := vc.leaseClient.Get(context.TODO(), "kyverno", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
vc.log.WithName("UpdateLastRequestTimestmap").Error(err, "Lease 'kyverno' not found. Starting clean-up...")
|
||||
return err
|
||||
}
|
||||
|
||||
ann = lease.GetAnnotations()
|
||||
if ann == nil {
|
||||
ann = map[string]string{}
|
||||
ann[annWebhookStatus] = status
|
||||
}
|
||||
|
||||
leaseStatus, ok := ann[annWebhookStatus]
|
||||
if ok {
|
||||
if leaseStatus == status {
|
||||
logger.V(4).Info(fmt.Sprintf("annotation %s already set to '%s'", annWebhookStatus, status))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ann[annWebhookStatus] = status
|
||||
lease.SetAnnotations(ann)
|
||||
|
||||
_, err = vc.leaseClient.Update(context.TODO(), lease, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "key %s, val %s", annWebhookStatus, status)
|
||||
}
|
||||
|
||||
logger.Info("updated lease annotation", "key", annWebhookStatus, "val", status)
|
||||
|
||||
// create event on kyverno deployment
|
||||
createStatusUpdateEvent(status, vc.eventGen)
|
||||
return nil
|
||||
}
|
||||
|
||||
func createStatusUpdateEvent(status string, eventGen event.Interface) {
|
||||
e := event.Info{}
|
||||
e.Kind = "Lease"
|
||||
e.Namespace = config.KyvernoNamespace()
|
||||
e.Name = leaseName
|
||||
e.Reason = "Update"
|
||||
e.Message = fmt.Sprintf("admission control webhook active status changed to %s", status)
|
||||
eventGen.Add(e)
|
||||
}
|
||||
|
||||
func (vc statusControl) UpdateLastRequestTimestmap(new time.Time) error {
|
||||
lease, err := vc.leaseClient.Get(context.TODO(), leaseName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
vc.log.WithName("UpdateLastRequestTimestmap").Error(err, "Lease 'kyverno' not found. Starting clean-up...")
|
||||
return err
|
||||
}
|
||||
|
||||
// add label to lease
|
||||
label := lease.GetLabels()
|
||||
if len(label) == 0 {
|
||||
label = make(map[string]string)
|
||||
label["app.kubernetes.io/name"] = kyvernov1.ValueKyvernoApp
|
||||
}
|
||||
lease.SetLabels(label)
|
||||
|
||||
annotation := lease.GetAnnotations()
|
||||
if annotation == nil {
|
||||
annotation = make(map[string]string)
|
||||
}
|
||||
|
||||
t, err := new.MarshalText()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal timestamp")
|
||||
}
|
||||
|
||||
annotation[annLastRequestTime] = string(t)
|
||||
lease.SetAnnotations(annotation)
|
||||
|
||||
// update annotations in lease
|
||||
_, err = vc.leaseClient.Update(context.TODO(), lease, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update annotation %s for deployment %s in namespace %s", annLastRequestTime, lease.GetName(), lease.GetNamespace())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"github.com/kyverno/kyverno/pkg/tracing"
|
||||
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||
"github.com/kyverno/kyverno/pkg/webhookconfig"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
)
|
||||
|
@ -99,9 +98,8 @@ func Filter(c config.Configuration, inner AdmissionHandler) AdmissionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func Verify(m *webhookconfig.Monitor) AdmissionHandler {
|
||||
func Verify() AdmissionHandler {
|
||||
return func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
|
||||
logger.V(6).Info("incoming request", "last admission request timestamp", m.Time())
|
||||
return admissionutils.Response(true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/webhookconfig"
|
||||
)
|
||||
|
||||
func Monitor(m *webhookconfig.Monitor, inner http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
m.SetTime(time.Now())
|
||||
inner(w, r)
|
||||
}
|
||||
}
|
|
@ -2,10 +2,10 @@ package handlers
|
|||
|
||||
import "net/http"
|
||||
|
||||
func Probe(check func() error) http.HandlerFunc {
|
||||
func Probe(check func() bool) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if check != nil {
|
||||
if err := check(); err != nil {
|
||||
if !check() {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
@ -15,9 +14,14 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/toggle"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||
"github.com/kyverno/kyverno/pkg/webhookconfig"
|
||||
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
|
||||
runtimeutils "github.com/kyverno/kyverno/pkg/utils/runtime"
|
||||
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
coordinationv1 "k8s.io/api/coordination/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
|
@ -45,9 +49,12 @@ type ResourceHandlers interface {
|
|||
}
|
||||
|
||||
type server struct {
|
||||
server *http.Server
|
||||
webhookRegister *webhookconfig.Register
|
||||
cleanUp chan struct{}
|
||||
server *http.Server
|
||||
runtime runtimeutils.Runtime
|
||||
mwcClient controllerutils.DeleteClient[*admissionregistrationv1.MutatingWebhookConfiguration]
|
||||
vwcClient controllerutils.DeleteClient[*admissionregistrationv1.ValidatingWebhookConfiguration]
|
||||
leaseClient controllerutils.DeleteClient[*coordinationv1.Lease]
|
||||
cleanUp chan struct{}
|
||||
}
|
||||
|
||||
type TlsProvider func() ([]byte, []byte, error)
|
||||
|
@ -56,22 +63,24 @@ type TlsProvider func() ([]byte, []byte, error)
|
|||
func NewServer(
|
||||
policyHandlers PolicyHandlers,
|
||||
resourceHandlers ResourceHandlers,
|
||||
tlsProvider TlsProvider,
|
||||
configuration config.Configuration,
|
||||
register *webhookconfig.Register,
|
||||
monitor *webhookconfig.Monitor,
|
||||
tlsProvider TlsProvider,
|
||||
mwcClient controllerutils.DeleteClient[*admissionregistrationv1.MutatingWebhookConfiguration],
|
||||
vwcClient controllerutils.DeleteClient[*admissionregistrationv1.ValidatingWebhookConfiguration],
|
||||
leaseClient controllerutils.DeleteClient[*coordinationv1.Lease],
|
||||
runtime runtimeutils.Runtime,
|
||||
) Server {
|
||||
mux := httprouter.New()
|
||||
resourceLogger := logger.WithName("resource")
|
||||
policyLogger := logger.WithName("policy")
|
||||
verifyLogger := logger.WithName("verify")
|
||||
registerWebhookHandlers(resourceLogger.WithName("mutate"), mux, config.MutatingWebhookServicePath, monitor, configuration, resourceHandlers.Mutate)
|
||||
registerWebhookHandlers(resourceLogger.WithName("validate"), mux, config.ValidatingWebhookServicePath, monitor, configuration, resourceHandlers.Validate)
|
||||
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, admission(policyLogger.WithName("mutate"), monitor, filter(configuration, policyHandlers.Mutate)))
|
||||
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, admission(policyLogger.WithName("validate"), monitor, filter(configuration, policyHandlers.Validate)))
|
||||
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, admission(verifyLogger.WithName("mutate"), monitor, handlers.Verify(monitor)))
|
||||
mux.HandlerFunc("GET", config.LivenessServicePath, handlers.Probe(register.Check))
|
||||
mux.HandlerFunc("GET", config.ReadinessServicePath, handlers.Probe(nil))
|
||||
registerWebhookHandlers(resourceLogger.WithName("mutate"), mux, config.MutatingWebhookServicePath, configuration, resourceHandlers.Mutate)
|
||||
registerWebhookHandlers(resourceLogger.WithName("validate"), mux, config.ValidatingWebhookServicePath, configuration, resourceHandlers.Validate)
|
||||
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, admission(policyLogger.WithName("mutate"), filter(configuration, policyHandlers.Mutate)))
|
||||
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, admission(policyLogger.WithName("validate"), filter(configuration, policyHandlers.Validate)))
|
||||
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, admission(verifyLogger.WithName("mutate"), handlers.Verify()))
|
||||
mux.HandlerFunc("GET", config.LivenessServicePath, handlers.Probe(runtime.IsLive))
|
||||
mux.HandlerFunc("GET", config.ReadinessServicePath, handlers.Probe(runtime.IsReady))
|
||||
return &server{
|
||||
server: &http.Server{
|
||||
Addr: ":9443",
|
||||
|
@ -94,8 +103,11 @@ func NewServer(
|
|||
WriteTimeout: 30 * time.Second,
|
||||
ReadHeaderTimeout: 30 * time.Second,
|
||||
},
|
||||
webhookRegister: register,
|
||||
cleanUp: make(chan struct{}),
|
||||
mwcClient: mwcClient,
|
||||
vwcClient: vwcClient,
|
||||
leaseClient: leaseClient,
|
||||
runtime: runtime,
|
||||
cleanUp: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,13 +138,29 @@ func (s *server) Cleanup() <-chan struct{} {
|
|||
}
|
||||
|
||||
func (s *server) cleanup(ctx context.Context) {
|
||||
cleanupKyvernoResource := s.webhookRegister.ShouldCleanupKyvernoResource()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go s.webhookRegister.Remove(cleanupKyvernoResource, &wg)
|
||||
go s.webhookRegister.ResetPolicyStatus(cleanupKyvernoResource, &wg)
|
||||
wg.Wait()
|
||||
if s.runtime.IsGoingDown() {
|
||||
deleteLease := func(name string) {
|
||||
if err := s.leaseClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
||||
logger.Error(err, "failed to clean up lease", "name", name)
|
||||
}
|
||||
}
|
||||
deleteVwc := func(name string) {
|
||||
if err := s.vwcClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
||||
logger.Error(err, "failed to clean up validating webhook configuration", "name", name)
|
||||
}
|
||||
}
|
||||
deleteMwc := func(name string) {
|
||||
if err := s.mwcClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
|
||||
logger.Error(err, "failed to clean up mutating webhook configuration", "name", name)
|
||||
}
|
||||
}
|
||||
deleteLease("kyvernopre-lock")
|
||||
deleteVwc(config.ValidatingWebhookConfigurationName)
|
||||
deleteVwc(config.PolicyValidatingWebhookConfigurationName)
|
||||
deleteMwc(config.MutatingWebhookConfigurationName)
|
||||
deleteMwc(config.PolicyMutatingWebhookConfigurationName)
|
||||
deleteMwc(config.VerifyMutatingWebhookConfigurationName)
|
||||
}
|
||||
close(s.cleanUp)
|
||||
}
|
||||
|
||||
|
@ -162,31 +190,30 @@ func filter(configuration config.Configuration, inner handlers.AdmissionHandler)
|
|||
return handlers.Filter(configuration, inner)
|
||||
}
|
||||
|
||||
func admission(logger logr.Logger, monitor *webhookconfig.Monitor, inner handlers.AdmissionHandler) http.HandlerFunc {
|
||||
return handlers.Monitor(monitor, handlers.Admission(logger, protect(inner)))
|
||||
func admission(logger logr.Logger, inner handlers.AdmissionHandler) http.HandlerFunc {
|
||||
return handlers.Admission(logger, protect(inner))
|
||||
}
|
||||
|
||||
func registerWebhookHandlers(
|
||||
logger logr.Logger,
|
||||
mux *httprouter.Router,
|
||||
basePath string,
|
||||
monitor *webhookconfig.Monitor,
|
||||
configuration config.Configuration,
|
||||
handlerFunc func(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse,
|
||||
) {
|
||||
mux.HandlerFunc("POST", basePath, admission(logger, monitor, filter(
|
||||
mux.HandlerFunc("POST", basePath, admission(logger, filter(
|
||||
configuration,
|
||||
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
|
||||
return handlerFunc(logger, request, "all", startTime)
|
||||
})),
|
||||
)
|
||||
mux.HandlerFunc("POST", basePath+"/fail", admission(logger, monitor, filter(
|
||||
mux.HandlerFunc("POST", basePath+"/fail", admission(logger, filter(
|
||||
configuration,
|
||||
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
|
||||
return handlerFunc(logger, request, "fail", startTime)
|
||||
})),
|
||||
)
|
||||
mux.HandlerFunc("POST", basePath+"/ignore", admission(logger, monitor, filter(
|
||||
mux.HandlerFunc("POST", basePath+"/ignore", admission(logger, filter(
|
||||
configuration,
|
||||
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
|
||||
return handlerFunc(logger, request, "ignore", startTime)
|
||||
|
|
Loading…
Add table
Reference in a new issue