From e022084dd0e57d48095ecd10f546c59b737272bc Mon Sep 17 00:00:00 2001 From: shivkumar dudhani <shivkumar@nirmata.com> Date: Wed, 30 Oct 2019 13:39:19 -0700 Subject: [PATCH] add checker to verify if mutatingwebhook is enabled or not + refactoring --- main.go | 8 +-- pkg/checker/checker.go | 114 ++++++++++++++++++++++++++++++ pkg/checker/status.go | 114 ++++++++++++++++++++++++++++++ pkg/config/config.go | 5 ++ pkg/webhookconfig/checker.go | 78 ++++++++++++++++++++ pkg/webhookconfig/policy.go | 41 +++++++++++ pkg/webhookconfig/registration.go | 80 ++++++++++----------- pkg/webhooks/checker.go | 89 ++--------------------- pkg/webhooks/mutation.go | 2 +- pkg/webhooks/policyvalidation.go | 1 - pkg/webhooks/server.go | 39 ++++++---- pkg/webhooks/validation.go | 4 +- 12 files changed, 432 insertions(+), 143 deletions(-) create mode 100644 pkg/checker/checker.go create mode 100644 pkg/checker/status.go create mode 100644 pkg/webhookconfig/checker.go diff --git a/main.go b/main.go index 45d60fd584..60ea2d6688 100644 --- a/main.go +++ b/main.go @@ -31,9 +31,6 @@ var ( filterK8Resources string ) -// TODO: tune resync time differently for each informer -const defaultReSyncTime = 10 * time.Second - func main() { defer glog.Flush() printVersionInfo() @@ -162,7 +159,10 @@ func main() { go egen.Run(1, stopCh) go nsc.Run(1, stopCh) - //TODO add WG for the go routines? + // verifys if the admission control is enabled and active + // resync: 60 seconds + // deadline: 60 seconds (send request) + // max deadline: deadline*3 (set the deployment annotation as false) server.RunAsync(stopCh) <-stopCh diff --git a/pkg/checker/checker.go b/pkg/checker/checker.go new file mode 100644 index 0000000000..62fe232abe --- /dev/null +++ b/pkg/checker/checker.go @@ -0,0 +1,114 @@ +package checker + +import ( + "sync" + "time" + + "github.com/golang/glog" + kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1" + dclient "github.com/nirmata/kyverno/pkg/dclient" + "k8s.io/apimachinery/pkg/labels" +) + +//MaxRetryCount defines the max deadline count +const MaxRetryCount int = 3 + +// LastReqTime +type LastReqTime struct { + t time.Time + mu sync.RWMutex +} + +func (t *LastReqTime) Time() time.Time { + t.mu.RLock() + defer t.mu.RUnlock() + return t.t +} + +func (t *LastReqTime) SetTime(tm time.Time) { + t.mu.Lock() + defer t.mu.Unlock() + glog.V(4).Info("updating last request time") + t.t = tm +} + +func NewLastReqTime() *LastReqTime { + return &LastReqTime{ + t: time.Now(), + } +} + +func checkIfPolicyWithMutateAndGenerateExists(pLister kyvernolister.ClusterPolicyLister) bool { + policies, err := pLister.ListResources(labels.NewSelector()) + if err != nil { + glog.Error() + } + for _, policy := range policies { + if policy.HasMutateOrValidate() { + // as there exists one policy with mutate or validate rule + // so there must be a webhook configuration on resource + return true + } + } + return false +} + +//Run runs the checker and verify the resource update +func (t *LastReqTime) Run(pLister kyvernolister.ClusterPolicyLister, client *dclient.Client, defaultResync time.Duration, deadline time.Duration, stopCh <-chan struct{}) { + glog.V(2).Infof("starting default resync for webhook checker with resync time %d", defaultResync) + maxDeadline := deadline * time.Duration(MaxRetryCount) + ticker := time.NewTicker(defaultResync) + var statuscontrol StatusInterface + /// interface to update and increment kyverno webhook status via annotations + statuscontrol = NewVerifyControl(client) + // send the initial update status + if checkIfPolicyWithMutateAndGenerateExists(pLister) { + if err := statuscontrol.SuccessStatus(); err != nil { + glog.Error(err) + } + } + + defer ticker.Stop() + // - has recieved request -> set webhookstatus as "True" + // - no requests recieved + // -> if greater than deadline, send update request + // -> if greater than maxDeadline, send failed status update + for { + select { + case <-ticker.C: + // if there are no policies then we dont have a webhook on resource. + // we indirectly check if the resource + if !checkIfPolicyWithMutateAndGenerateExists(pLister) { + continue + } + // get current time + timeDiff := time.Since(t.Time()) + if timeDiff > maxDeadline { + glog.Infof("failed to recieve any request for more than %v ", maxDeadline) + glog.Info("Admission Control failing: Webhook is not recieving requests forwarded by api-server as per webhook configurations") + // set the status unavailable + if err := statuscontrol.FailedStatus(); err != nil { + glog.Error(err) + } + continue + } + if timeDiff > deadline { + glog.Info("Admission Control failing: Webhook is not recieving requests forwarded by api-server as per webhook configurations") + // send request to update the kyverno deployment + if err := statuscontrol.IncrementAnnotation(); err != nil { + glog.Error(err) + } + continue + } + // if the status was false before then we update it to true + // send request to update the kyverno deployment + if err := statuscontrol.SuccessStatus(); err != nil { + glog.Error(err) + } + case <-stopCh: + // handler termination signal + glog.V(2).Infof("stopping default resync for webhook checker") + return + } + } +} diff --git a/pkg/checker/status.go b/pkg/checker/status.go new file mode 100644 index 0000000000..240fd89a1d --- /dev/null +++ b/pkg/checker/status.go @@ -0,0 +1,114 @@ +package checker + +import ( + "strconv" + + "github.com/golang/glog" + dclient "github.com/nirmata/kyverno/pkg/dclient" +) + +const deployName string = "kyverno" +const deployNamespace string = "kyverno" + +const annCounter string = "kyverno.io/generationCounter" +const annWebhookStats string = "kyverno.io/webhookActive" + +//StatusInterface provides api to update webhook active annotations on kyverno deployments +type StatusInterface interface { + // Increments generation counter annotation + IncrementAnnotation() error + // update annotation to inform webhook is active + SuccessStatus() error + // update annotation to inform webhook is inactive + FailedStatus() error +} + +//StatusControl controls the webhook status +type StatusControl struct { + client *dclient.Client +} + +//SuccessStatus ... +func (vc StatusControl) SuccessStatus() error { + return vc.setStatus("true") +} + +//FailedStatus ... +func (vc StatusControl) FailedStatus() error { + return vc.setStatus("false") +} + +// NewVerifyControl ... +func NewVerifyControl(client *dclient.Client) *StatusControl { + return &StatusControl{ + client: client, + } +} + +func (vc StatusControl) setStatus(status string) error { + glog.Infof("setting deployment %s in ns %s annotation %s to %s", deployName, deployNamespace, annWebhookStats, status) + var ann map[string]string + var err error + deploy, err := vc.client.GetResource("Deployment", deployNamespace, deployName) + if err != nil { + glog.V(4).Infof("failed to get deployment %s in namespace %s: %v", deployName, deployNamespace, err) + return err + } + ann = deploy.GetAnnotations() + if ann == nil { + ann = map[string]string{} + ann[annWebhookStats] = status + } + webhookAction, ok := ann[annWebhookStats] + if ok { + // annotatiaion is present + if webhookAction == status { + glog.V(4).Infof("annotation %s already set to '%s'", annWebhookStats, status) + return nil + } + } + // set the status + ann[annWebhookStats] = status + deploy.SetAnnotations(ann) + // update counter + _, err = vc.client.UpdateResource("Deployment", deployNamespace, deploy, false) + if err != nil { + glog.V(4).Infof("failed to update annotation %s for deployment %s in namespace %s: %v", annWebhookStats, deployName, deployNamespace, err) + return err + } + return nil +} + +//IncrementAnnotation ... +func (vc StatusControl) IncrementAnnotation() error { + glog.Infof("setting deployment %s in ns %s annotation %s", deployName, deployNamespace, annCounter) + var ann map[string]string + var err error + deploy, err := vc.client.GetResource("Deployment", deployNamespace, deployName) + if err != nil { + glog.V(4).Infof("failed to get deployment %s in namespace %s: %v", deployName, deployNamespace, err) + return err + } + ann = deploy.GetAnnotations() + if ann == nil { + ann = map[string]string{} + ann[annCounter] = "0" + } + counter, err := strconv.Atoi(ann[annCounter]) + if err != nil { + glog.V(4).Infof("failed to parse string: %v", err) + return err + } + // increment counter + counter++ + ann[annCounter] = strconv.Itoa(counter) + glog.Info("incrementing annotation %s counter to %d", annCounter, counter) + deploy.SetAnnotations(ann) + // update counter + _, err = vc.client.UpdateResource("Deployment", deployNamespace, deploy, false) + if err != nil { + glog.V(4).Infof("failed to update annotation %s for deployment %s in namespace %s: %v", annCounter, deployName, deployNamespace, err) + return err + } + return nil +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 0fc8a9fe48..8cb472a86d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -15,6 +15,10 @@ const ( // ValidatingWebhookConfigurationDebug = "kyverno-validating-webhook-cfg-debug" // ValidatingWebhookName = "nirmata.kyverno.policy-validating-webhook" + VerifyMutatingWebhookConfigurationName = "kyverno-verify-mutating-webhook-cfg" + VerifyMutatingWebhookConfigurationDebugName = "kyverno-verify-mutating-webhook-cfg-debug" + VerifyMutatingWebhookName = "nirmata.kyverno.verify-mutating-webhook" + PolicyValidatingWebhookConfigurationName = "kyverno-policy-validating-webhook-cfg" PolicyValidatingWebhookConfigurationDebugName = "kyverno-policy-validating-webhook-cfg-debug" PolicyValidatingWebhookName = "nirmata.kyverno.policy-validating-webhook" @@ -36,6 +40,7 @@ var ( ValidatingWebhookServicePath = "/validate" PolicyValidatingWebhookServicePath = "/policyvalidate" PolicyMutatingWebhookServicePath = "/policymutate" + VerifyMutatingWebhookServicePath = "/verifymutate" SupportedKinds = []string{ "ConfigMap", diff --git a/pkg/webhookconfig/checker.go b/pkg/webhookconfig/checker.go new file mode 100644 index 0000000000..3494fc159f --- /dev/null +++ b/pkg/webhookconfig/checker.go @@ -0,0 +1,78 @@ +package webhookconfig + +import ( + "fmt" + + "github.com/golang/glog" + "github.com/nirmata/kyverno/pkg/config" + admregapi "k8s.io/api/admissionregistration/v1beta1" + errorsapi "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (wrc *WebhookRegistrationClient) constructVerifyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration { + return &admregapi.MutatingWebhookConfiguration{ + ObjectMeta: v1.ObjectMeta{ + Name: config.VerifyMutatingWebhookConfigurationName, + OwnerReferences: []v1.OwnerReference{ + wrc.constructOwner(), + }, + }, + Webhooks: []admregapi.Webhook{ + generateWebhook( + config.VerifyMutatingWebhookName, + config.VerifyMutatingWebhookServicePath, + caData, + true, + wrc.timeoutSeconds, + "deployments/*", + "apps", + "v1", + []admregapi.OperationType{admregapi.Update}, + ), + }, + } +} + +func (wrc *WebhookRegistrationClient) constructDebugVerifyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration { + url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.VerifyMutatingWebhookServicePath) + glog.V(4).Infof("Debug VerifyMutatingWebhookConfig is registered with url %s\n", url) + return &admregapi.MutatingWebhookConfiguration{ + ObjectMeta: v1.ObjectMeta{ + Name: config.VerifyMutatingWebhookConfigurationDebugName, + }, + Webhooks: []admregapi.Webhook{ + generateDebugWebhook( + config.VerifyMutatingWebhookName, + url, + caData, + true, + wrc.timeoutSeconds, + "deployments/*", + "apps", + "v1", + []admregapi.OperationType{admregapi.Update}, + ), + }, + } +} + +func (wrc *WebhookRegistrationClient) removeVerifyWebhookMutatingWebhookConfig() { + // Muating webhook configuration + var err error + var mutatingConfig string + if wrc.serverIP != "" { + mutatingConfig = config.VerifyMutatingWebhookConfigurationDebugName + } else { + mutatingConfig = config.VerifyMutatingWebhookConfigurationName + } + glog.V(4).Infof("removing webhook configuration %s", mutatingConfig) + err = wrc.registrationClient.MutatingWebhookConfigurations().Delete(mutatingConfig, &v1.DeleteOptions{}) + if errorsapi.IsNotFound(err) { + glog.V(4).Infof("verify webhook configuration %s, does not exits. not deleting", mutatingConfig) + } else if err != nil { + glog.Errorf("failed to delete verify webhook configuration %s: %v", mutatingConfig, err) + } else { + glog.V(4).Infof("succesfully deleted verify webhook configuration %s", mutatingConfig) + } +} diff --git a/pkg/webhookconfig/policy.go b/pkg/webhookconfig/policy.go index 60a7b5bfae..41fc8e81af 100644 --- a/pkg/webhookconfig/policy.go +++ b/pkg/webhookconfig/policy.go @@ -6,6 +6,7 @@ import ( "github.com/golang/glog" "github.com/nirmata/kyverno/pkg/config" admregapi "k8s.io/api/admissionregistration/v1beta1" + errorsapi "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -104,3 +105,43 @@ func (wrc *WebhookRegistrationClient) contructDebugPolicyMutatingWebhookConfig(c }, } } + +// removePolicyWebhookConfigurations removes mutating and validating webhook configurations, if already presnt +// webhookConfigurations are re-created later +func (wrc *WebhookRegistrationClient) removePolicyWebhookConfigurations() { + // Validating webhook configuration + var err error + var validatingConfig string + if wrc.serverIP != "" { + validatingConfig = config.PolicyValidatingWebhookConfigurationDebugName + } else { + validatingConfig = config.PolicyValidatingWebhookConfigurationName + } + glog.V(4).Infof("removing webhook configuration %s", validatingConfig) + err = wrc.registrationClient.ValidatingWebhookConfigurations().Delete(validatingConfig, &v1.DeleteOptions{}) + if errorsapi.IsNotFound(err) { + glog.V(4).Infof("policy webhook configuration %s, does not exits. not deleting", validatingConfig) + } else if err != nil { + glog.Errorf("failed to delete policy webhook configuration %s: %v", validatingConfig, err) + } else { + glog.V(4).Infof("succesfully deleted policy webhook configuration %s", validatingConfig) + } + + // Mutating webhook configuration + var mutatingConfig string + if wrc.serverIP != "" { + mutatingConfig = config.PolicyMutatingWebhookConfigurationDebugName + } else { + mutatingConfig = config.PolicyMutatingWebhookConfigurationName + } + + glog.V(4).Infof("removing webhook configuration %s", mutatingConfig) + err = wrc.registrationClient.MutatingWebhookConfigurations().Delete(mutatingConfig, &v1.DeleteOptions{}) + if errorsapi.IsNotFound(err) { + glog.V(4).Infof("policy webhook configuration %s, does not exits. not deleting", mutatingConfig) + } else if err != nil { + glog.Errorf("failed to delete policy webhook configuration %s: %v", mutatingConfig, err) + } else { + glog.V(4).Infof("succesfully deleted policy webhook configuration %s", mutatingConfig) + } +} diff --git a/pkg/webhookconfig/registration.go b/pkg/webhookconfig/registration.go index 2511a13167..409f9f66a0 100644 --- a/pkg/webhookconfig/registration.go +++ b/pkg/webhookconfig/registration.go @@ -64,6 +64,13 @@ func (wrc *WebhookRegistrationClient) Register() error { if err := wrc.createPolicyMutatingWebhookConfiguration(); err != nil { return err } + + // create Verify mutating webhook configuration resource + // that is used to check if admission control is enabled or not + if err := wrc.createVerifyMutatingWebhookConfiguration(); err != nil { + return err + } + return nil } @@ -184,6 +191,36 @@ func (wrc *WebhookRegistrationClient) createPolicyMutatingWebhookConfiguration() return nil } +func (wrc *WebhookRegistrationClient) createVerifyMutatingWebhookConfiguration() error { + var caData []byte + var config *admregapi.MutatingWebhookConfiguration + + // read CA data from + // 1) secret(config) + // 2) kubeconfig + if caData = wrc.readCaData(); caData == nil { + return errors.New("Unable to extract CA data from configuration") + } + + // if serverIP is specified we assume its debug mode + if wrc.serverIP != "" { + // debug mode + // clientConfig - URL + config = wrc.constructDebugVerifyMutatingWebhookConfig(caData) + } else { + // clientConfig - service + config = wrc.constructVerifyMutatingWebhookConfig(caData) + } + + // create mutating webhook configuration resource + if _, err := wrc.registrationClient.MutatingWebhookConfigurations().Create(config); err != nil { + return err + } + + glog.V(4).Infof("created Mutating Webhook Configuration %s ", config.Name) + return nil +} + // DeregisterAll deletes webhook configs from cluster // This function does not fail on error: // Register will fail if the config exists, so there is no need to fail on error @@ -198,44 +235,7 @@ func (wrc *WebhookRegistrationClient) removeWebhookConfigurations() { // mutating and validating webhook configurtion for Policy CRD resource wrc.removePolicyWebhookConfigurations() -} - -// removePolicyWebhookConfigurations removes mutating and validating webhook configurations, if already presnt -// webhookConfigurations are re-created later -func (wrc *WebhookRegistrationClient) removePolicyWebhookConfigurations() { - // Validating webhook configuration - var err error - var validatingConfig string - if wrc.serverIP != "" { - validatingConfig = config.PolicyValidatingWebhookConfigurationDebugName - } else { - validatingConfig = config.PolicyValidatingWebhookConfigurationName - } - glog.V(4).Infof("removing webhook configuration %s", validatingConfig) - err = wrc.registrationClient.ValidatingWebhookConfigurations().Delete(validatingConfig, &v1.DeleteOptions{}) - if errorsapi.IsNotFound(err) { - glog.V(4).Infof("policy webhook configuration %s, does not exits. not deleting", validatingConfig) - } else if err != nil { - glog.Errorf("failed to delete policy webhook configuration %s: %v", validatingConfig, err) - } else { - glog.V(4).Infof("succesfully deleted policy webhook configuration %s", validatingConfig) - } - - // Mutating webhook configuration - var mutatingConfig string - if wrc.serverIP != "" { - mutatingConfig = config.PolicyMutatingWebhookConfigurationDebugName - } else { - mutatingConfig = config.PolicyMutatingWebhookConfigurationName - } - - glog.V(4).Infof("removing webhook configuration %s", mutatingConfig) - err = wrc.registrationClient.MutatingWebhookConfigurations().Delete(mutatingConfig, &v1.DeleteOptions{}) - if errorsapi.IsNotFound(err) { - glog.V(4).Infof("policy webhook configuration %s, does not exits. not deleting", mutatingConfig) - } else if err != nil { - glog.Errorf("failed to delete policy webhook configuration %s: %v", mutatingConfig, err) - } else { - glog.V(4).Infof("succesfully deleted policy webhook configuration %s", mutatingConfig) - } + + // muating webhook configuration use to verify if admission control flow is working or not + wrc.removeVerifyWebhookMutatingWebhookConfig() } diff --git a/pkg/webhooks/checker.go b/pkg/webhooks/checker.go index 828b6dc0ca..e6ccfc41c9 100644 --- a/pkg/webhooks/checker.go +++ b/pkg/webhooks/checker.go @@ -1,91 +1,14 @@ package webhooks import ( - "sync" - "time" - "github.com/golang/glog" - kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" - kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned" + "k8s.io/api/admission/v1beta1" ) -const MaxRetryCount int = 3 - -// Last Request Time -type LastReqTime struct { - t time.Time - mu sync.RWMutex - RetryCount int -} - -func (t *LastReqTime) Time() time.Time { - t.mu.RLock() - defer t.mu.RUnlock() - return t.t -} - -func (t *LastReqTime) SetTime(tm time.Time) { - t.mu.Lock() - defer t.mu.Unlock() - t.t = tm - t.RetryCount = MaxRetryCount -} - -func (t *LastReqTime) DecrementRetryCounter() { - t.mu.Lock() - defer t.mu.Unlock() - t.RetryCount-- -} - -func NewLastReqTime() *LastReqTime { - return &LastReqTime{ - t: time.Now(), +func (ws *WebhookServer) handleVerifyRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { + glog.V(4).Infof("Receive request in mutating webhook '/verify': Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", + request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation) + return &v1beta1.AdmissionResponse{ + Allowed: true, } } - -func (t *LastReqTime) checker(kyvernoClient *kyvernoclient.Clientset, defaultResync time.Duration, deadline time.Duration, stopCh <-chan struct{}) { - sendDummyRequest := func(kyvernoClient *kyvernoclient.Clientset) { - dummyPolicy := kyverno.ClusterPolicy{ - Spec: kyverno.Spec{ - Rules: []kyverno.Rule{ - kyverno.Rule{ - Name: "dummyPolicy", - MatchResources: kyverno.MatchResources{ - ResourceDescription: kyverno.ResourceDescription{ - Kinds: []string{"Deployment"}, - }, - }, - Validation: kyverno.Validation{ - Message: "dummy validation policy rule", - Pattern: "dummypattern", - }, - }, - }, - }, - } - // this - kyvernoClient.KyvernoV1alpha1().ClusterPolicies().Create(&dummyPolicy) - } - glog.V(2).Infof("starting default resync for webhook checker with resync time %d", defaultResync) - ticker := time.NewTicker(defaultResync) - defer ticker.Stop() - for { - select { - case <-ticker.C: - // get current time - timeDiff := time.Since(t.Time()) - if timeDiff > deadline { - if t.RetryCount == 0 { - // set the status unavailable - } - t.DecrementRetryCounter() - // send request again - } - - case <-stopCh: - // handler termination signal - break - } - } - glog.V(2).Info("stopping default resync for webhook checker") -} diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index a6871631a9..f27100adc5 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -11,7 +11,7 @@ import ( ) // HandleMutation handles mutating webhook admission request -func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool, []byte, string) { +func (ws *WebhookServer) handleMutation(request *v1beta1.AdmissionRequest) (bool, []byte, string) { glog.V(4).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation) diff --git a/pkg/webhooks/policyvalidation.go b/pkg/webhooks/policyvalidation.go index 7f7d95dd56..b42c19368a 100644 --- a/pkg/webhooks/policyvalidation.go +++ b/pkg/webhooks/policyvalidation.go @@ -27,7 +27,6 @@ func (ws *WebhookServer) handlePolicyValidation(request *v1beta1.AdmissionReques Message: fmt.Sprintf("Failed to unmarshal policy admission request err %v", err), }} } - if err := policyvalidate.Validate(*policy); err != nil { admissionResp = &v1beta1.AdmissionResponse{ Allowed: false, diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index f70dfbeab8..204d7a4f81 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -11,6 +11,7 @@ import ( "time" "github.com/golang/glog" + "github.com/nirmata/kyverno/pkg/checker" kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned" kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1alpha1" kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1" @@ -43,6 +44,8 @@ type WebhookServer struct { configHandler config.Interface // channel for cleanup notification cleanUp chan<- struct{} + // last request time + lastReqTime *checker.LastReqTime } // NewWebhookServer creates new instance of WebhookServer accordingly to given configuration @@ -83,6 +86,7 @@ func NewWebhookServer( policyStatus: policyStatus, configHandler: configHandler, cleanUp: cleanUp, + lastReqTime: checker.NewLastReqTime(), } mux := http.NewServeMux() mux.HandleFunc(config.MutatingWebhookServicePath, ws.serve) @@ -103,6 +107,9 @@ func NewWebhookServer( // Main server endpoint for all requests func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { + // for every request recieved on the ep update last request time, + // this is used to verify admission control + ws.lastReqTime.SetTime(time.Now()) admissionReview := ws.bodyToAdmissionReview(r, w) if admissionReview == nil { return @@ -114,19 +121,24 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { // Do not process the admission requests for kinds that are in filterKinds for filtering request := admissionReview.Request - if !ws.configHandler.ToFilter(request.Kind.Kind, request.Namespace, request.Name) { - // Resource CREATE - // Resource UPDATE - switch r.URL.Path { - case config.MutatingWebhookServicePath: + switch r.URL.Path { + case config.VerifyMutatingWebhookServicePath: + // we do not apply filters as this endpoint is used explicity + // to watch kyveno deployment and verify if admission control is enabled + admissionReview.Response = ws.handleVerifyRequest(request) + case config.MutatingWebhookServicePath: + if !ws.configHandler.ToFilter(request.Kind.Kind, request.Namespace, request.Name) { admissionReview.Response = ws.handleAdmissionRequest(request) - case config.PolicyValidatingWebhookServicePath: + } + case config.PolicyValidatingWebhookServicePath: + if !ws.configHandler.ToFilter(request.Kind.Kind, request.Namespace, request.Name) { admissionReview.Response = ws.handlePolicyValidation(request) - case config.PolicyMutatingWebhookServicePath: + } + case config.PolicyMutatingWebhookServicePath: + if !ws.configHandler.ToFilter(request.Kind.Kind, request.Namespace, request.Name) { admissionReview.Response = ws.handlePolicyMutation(request) } } - admissionReview.Response.UID = request.UID responseJSON, err := json.Marshal(admissionReview) @@ -143,7 +155,7 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { // MUTATION - ok, patches, msg := ws.HandleMutation(request) + ok, patches, msg := ws.handleMutation(request) if !ok { glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name) return &v1beta1.AdmissionResponse{ @@ -159,7 +171,7 @@ func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionReques patchedResource := processResourceWithPatches(patches, request.Object.Raw) // VALIDATION - ok, msg = ws.HandleValidation(request, patchedResource) + ok, msg = ws.handleValidation(request, patchedResource) if !ok { glog.V(4).Infof("Deny admission request: %v/%s/%s", request.Kind, request.Namespace, request.Name) return &v1beta1.AdmissionResponse{ @@ -192,8 +204,11 @@ func (ws *WebhookServer) RunAsync(stopCh <-chan struct{}) { } }(ws) glog.Info("Started Webhook Server") - - go checker(10*time.Second, stopCh) + // verifys if the admission control is enabled and active + // resync: 60 seconds + // deadline: 60 seconds (send request) + // max deadline: deadline*3 (set the deployment annotation as false) + go ws.lastReqTime.Run(ws.pLister, ws.client, 60*time.Second, 60*time.Second, stopCh) } // Stop TLS server and returns control after the server is shut down diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go index 7623f647ec..8fb269e24d 100644 --- a/pkg/webhooks/validation.go +++ b/pkg/webhooks/validation.go @@ -11,10 +11,10 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -// HandleValidation handles validating webhook admission request +// handleValidation handles validating webhook admission request // If there are no errors in validating rule we apply generation rules // patchedResource is the (resource + patches) after applying mutation rules -func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, patchedResource []byte) (bool, string) { +func (ws *WebhookServer) handleValidation(request *v1beta1.AdmissionRequest, patchedResource []byte) (bool, string) { glog.V(4).Infof("Receive request in validating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s", request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)