2020-11-26 16:07:06 -08:00
package webhookconfig
import (
2022-03-25 19:12:01 +05:30
"context"
2020-11-26 16:07:06 -08:00
"fmt"
"sync"
"time"
"github.com/go-logr/logr"
2022-03-16 23:23:46 +08:00
"github.com/kyverno/kyverno/pkg/config"
2020-11-26 16:07:06 -08:00
"github.com/kyverno/kyverno/pkg/event"
2021-03-16 11:31:04 -07:00
"github.com/kyverno/kyverno/pkg/tls"
2021-06-08 12:37:19 -07:00
"github.com/pkg/errors"
2022-03-25 19:12:01 +05:30
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-06-08 12:37:19 -07:00
"k8s.io/client-go/kubernetes"
2022-03-25 19:12:01 +05:30
coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
2020-11-26 16:07:06 -08:00
)
2022-05-17 08:19:03 +02:00
// maxRetryCount defines the max deadline count
2020-11-26 16:07:06 -08:00
const (
2020-11-30 11:22:20 -08:00
tickerInterval time . Duration = 30 * time . Second
2020-11-26 16:07:06 -08:00
idleCheckInterval time . Duration = 60 * time . Second
2021-06-08 12:37:19 -07:00
idleDeadline time . Duration = idleCheckInterval * 5
2020-11-26 16:07:06 -08:00
)
// 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.
//
2021-06-08 12:37:19 -07:00
// 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.
2020-11-26 16:07:06 -08:00
type Monitor struct {
2022-03-25 19:12:01 +05:30
// leaseClient is used to manage Kyverno lease
leaseClient coordinationv1 . LeaseInterface
2022-03-16 23:23:46 +08:00
2021-06-08 12:37:19 -07:00
// lastSeenRequestTime records the timestamp
// of the latest received admission request
lastSeenRequestTime time . Time
mu sync . RWMutex
log logr . Logger
2020-11-26 16:07:06 -08:00
}
2021-06-08 12:37:19 -07:00
// NewMonitor returns a new instance of webhook monitor
func NewMonitor ( kubeClient kubernetes . Interface , log logr . Logger ) ( * Monitor , error ) {
2021-03-16 11:31:04 -07:00
monitor := & Monitor {
2022-05-11 08:14:30 +02:00
leaseClient : kubeClient . CoordinationV1 ( ) . Leases ( config . KyvernoNamespace ( ) ) ,
2021-06-08 12:37:19 -07:00
lastSeenRequestTime : time . Now ( ) ,
log : log ,
2020-11-26 16:07:06 -08:00
}
2021-03-16 11:31:04 -07:00
2021-06-08 12:37:19 -07:00
return monitor , nil
2020-11-26 16:07:06 -08:00
}
2021-06-08 12:37:19 -07:00
// Time returns the last request time
2020-11-26 16:07:06 -08:00
func ( t * Monitor ) Time ( ) time . Time {
t . mu . RLock ( )
defer t . mu . RUnlock ( )
2021-06-08 12:37:19 -07:00
return t . lastSeenRequestTime
2020-11-26 16:07:06 -08:00
}
2021-06-08 12:37:19 -07:00
// SetTime updates the last request time
2020-11-26 16:07:06 -08:00
func ( t * Monitor ) SetTime ( tm time . Time ) {
t . mu . Lock ( )
defer t . mu . Unlock ( )
2021-06-08 12:37:19 -07:00
t . lastSeenRequestTime = tm
2021-03-16 11:31:04 -07:00
}
2021-06-08 12:37:19 -07:00
// Run runs the checker and verify the resource update
2021-03-16 11:31:04 -07:00
func ( t * Monitor ) Run ( register * Register , certRenewer * tls . CertRenewer , eventGen event . Interface , stopCh <- chan struct { } ) {
2021-10-05 00:15:09 -07:00
logger := t . log . WithName ( "webhookMonitor" )
2021-06-08 12:37:19 -07:00
2022-03-16 23:23:46 +08:00
logger . V ( 3 ) . Info ( "starting webhook monitor" , "interval" , idleCheckInterval . String ( ) )
2022-03-25 19:12:01 +05:30
status := newStatusControl ( t . leaseClient , eventGen , logger . WithName ( "WebhookStatusControl" ) )
2020-11-26 16:07:06 -08:00
ticker := time . NewTicker ( tickerInterval )
defer ticker . Stop ( )
2021-10-05 00:15:09 -07:00
createDefaultWebhook := register . createDefaultWebhook
2020-11-26 16:07:06 -08:00
for {
select {
2021-10-05 00:15:09 -07:00
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 )
}
}
2020-11-26 16:07:06 -08:00
case <- ticker . C :
2021-06-08 12:37:19 -07:00
err := registerWebhookIfNotPresent ( register , t . log . WithName ( "registerWebhookIfNotPresent" ) )
if err != nil {
t . log . Error ( err , "" )
2020-11-26 16:07:06 -08:00
}
2022-01-19 13:29:08 +05:30
// update namespaceSelector every 30 seconds
2022-03-16 23:23:46 +08:00
go func ( ) {
if register . autoUpdateWebhooks {
2022-07-11 17:21:20 +01:00
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" )
}
2022-03-16 23:23:46 +08:00
}
} ( )
2022-01-19 13:29:08 +05:30
2020-11-26 16:07:06 -08:00
timeDiff := time . Since ( t . Time ( ) )
2022-03-25 19:12:01 +05:30
lastRequestTimeFromAnn := lastRequestTimeFromAnnotation ( t . leaseClient , t . log . WithName ( "lastRequestTimeFromAnnotation" ) )
2021-06-15 18:39:22 -07:00
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 :
2022-03-16 23:23:46 +08:00
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
2020-11-26 16:07:06 -08:00
if err := status . failure ( ) ; err != nil {
logger . Error ( err , "failed to annotate deployment webhook status to failure" )
2022-03-16 23:23:46 +08:00
if err := register . Register ( ) ; err != nil {
logger . Error ( err , "Failed to register webhooks" )
}
2020-11-26 16:07:06 -08:00
}
2022-03-16 23:23:46 +08:00
continue
2021-06-15 18:39:22 -07:00
case timeDiff > 2 * idleCheckInterval :
2021-03-16 11:31:04 -07:00
if skipWebhookCheck ( register , logger . WithName ( "skipWebhookCheck" ) ) {
logger . Info ( "skip validating webhook status, Kyverno is in rolling update" )
continue
}
2020-11-26 16:07:06 -08:00
2021-06-08 12:37:19 -07:00
if t . Time ( ) . Before ( * lastRequestTimeFromAnn ) {
t . SetTime ( * lastRequestTimeFromAnn )
logger . V ( 3 ) . Info ( "updated in-memory timestamp" , "time" , lastRequestTimeFromAnn )
}
2021-06-15 18:39:22 -07:00
}
2021-06-08 12:37:19 -07:00
2021-06-15 18:39:22 -07:00
idleT := time . Since ( * lastRequestTimeFromAnn )
if idleT > idleCheckInterval {
if t . Time ( ) . After ( * lastRequestTimeFromAnn ) {
2022-03-16 23:23:46 +08:00
logger . V ( 3 ) . Info ( "updating annotation lastRequestTimestamp with the latest in-memory timestamp" , "time" , t . Time ( ) , "lastRequestTimestamp" , lastRequestTimeFromAnn )
2021-06-08 12:37:19 -07:00
if err := status . UpdateLastRequestTimestmap ( t . Time ( ) ) ; err != nil {
logger . Error ( err , "failed to update lastRequestTimestamp annotation" )
}
}
2020-11-26 16:07:06 -08:00
}
2021-06-15 18:39:22 -07:00
// 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" )
}
2021-06-08 12:37:19 -07:00
case <- stopCh :
// handler termination signal
logger . V ( 2 ) . Info ( "stopping webhook monitor" )
return
}
}
}
2021-05-04 22:10:01 -07:00
2021-06-08 12:37:19 -07:00
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
}
2021-03-16 11:31:04 -07:00
2021-06-08 12:37:19 -07:00
if err := register . Check ( ) ; err != nil {
logger . Error ( err , "missing webhooks" )
2021-03-16 11:31:04 -07:00
2021-06-08 12:37:19 -07:00
if err := register . Register ( ) ; err != nil {
return errors . Wrap ( err , "failed to register webhooks" )
}
}
2021-03-16 11:31:04 -07:00
2021-06-08 12:37:19 -07:00
return nil
}
2021-05-04 22:10:01 -07:00
2022-03-25 19:12:01 +05:30
func lastRequestTimeFromAnnotation ( leaseClient coordinationv1 . LeaseInterface , logger logr . Logger ) * time . Time {
lease , err := leaseClient . Get ( context . TODO ( ) , "kyverno" , metav1 . GetOptions { } )
2021-06-08 12:37:19 -07:00
if err != nil {
2022-03-25 19:12:01 +05:30
logger . Info ( "Lease 'kyverno' not found. Starting clean-up..." )
2021-06-08 12:37:19 -07:00
}
2021-03-16 11:31:04 -07:00
2022-03-25 19:12:01 +05:30
timeStamp := lease . GetAnnotations ( )
if timeStamp == nil {
2021-06-08 12:37:19 -07:00
logger . Info ( "timestamp not set in the annotation, setting" )
return nil
}
2021-03-16 11:31:04 -07:00
2022-03-25 19:12:01 +05:30
annTime , err := time . Parse ( time . RFC3339 , timeStamp [ annLastRequestTime ] )
2021-06-08 12:37:19 -07:00
if err != nil {
2022-03-25 19:12:01 +05:30
logger . Error ( err , "failed to parse timestamp annotation" , "timeStamp" , timeStamp [ annLastRequestTime ] )
2021-06-08 12:37:19 -07:00
return nil
2020-11-26 16:07:06 -08:00
}
2021-06-08 12:37:19 -07:00
return & annTime
2020-11-26 16:07:06 -08:00
}
2021-03-16 11:31:04 -07:00
// skipWebhookCheck returns true if Kyverno is in rolling update
func skipWebhookCheck ( register * Register , logger logr . Logger ) bool {
2022-05-02 12:58:04 +02:00
deploy , err := register . GetKubePolicyDeployment ( )
2021-03-16 11:31:04 -07:00
if err != nil {
logger . Info ( "unable to get Kyverno deployment" , "reason" , err . Error ( ) )
return false
}
2022-05-12 16:07:25 +02:00
return tls . IsKyvernoInRollingUpdate ( deploy )
2021-03-16 11:31:04 -07:00
}