From 277402ba4c6f1ab264e56ac3116a7c1a11d228c4 Mon Sep 17 00:00:00 2001 From: Yuvraj <10830562+evalsocket@users.noreply.github.com> Date: Mon, 18 May 2020 17:00:52 -0700 Subject: [PATCH] Feature - Add checks for k8s version when Kyverno starts (#831) * Added k8s version check for mutating and validating' * version check adde * middelware added * formate * Added timeout flag value to webhook server timeout middelware and refactore kubernetes version check * Fixed test cases * Removed log * Update kubernetes version check * Added check for mutate and validate * Skip Validation in handleValidateAdmissionRequest if kubernetes version is below 1.14 * Update return object AdmissionResponse * fixed condition for skiping mutation * Handle condition for skip feature in case of kubernetes version 1.14.2 --- cmd/initContainer/main.go | 43 ++-------------- pkg/utils/util.go | 39 ++++++++++++++ pkg/webhookconfig/registration.go | 5 ++ pkg/webhooks/middleware.go | 15 ++++++ pkg/webhooks/server.go | 85 ++++++++++++++++++------------- 5 files changed, 113 insertions(+), 74 deletions(-) create mode 100644 pkg/webhooks/middleware.go diff --git a/cmd/initContainer/main.go b/cmd/initContainer/main.go index e9e377cb7f..ba07577665 100644 --- a/cmd/initContainer/main.go +++ b/cmd/initContainer/main.go @@ -7,14 +7,13 @@ import ( "flag" "fmt" "os" - "regexp" - "strconv" "sync" "time" "github.com/nirmata/kyverno/pkg/config" client "github.com/nirmata/kyverno/pkg/dclient" "github.com/nirmata/kyverno/pkg/signal" + "github.com/nirmata/kyverno/pkg/utils" "k8s.io/apimachinery/pkg/api/errors" rest "k8s.io/client-go/rest" clientcmd "k8s.io/client-go/tools/clientcmd" @@ -63,7 +62,9 @@ func main() { // Exit for unsupported version of kubernetes cluster // https://github.com/nirmata/kyverno/issues/700 // - supported from v1.12.7+ - isVersionSupported(client) + if !utils.CompareKubernetesVersion(client, log.Log, 1, 12, 7) { + os.Exit(1) + } requests := []request{ // Resource @@ -222,39 +223,3 @@ func merge(done <-chan struct{}, stopCh <-chan struct{}, processes ...<-chan err }() return out } - -func isVersionSupported(client *client.Client) { - logger := log.Log - serverVersion, err := client.DiscoveryClient.GetServerVersion() - if err != nil { - logger.Error(err, "Failed to get kubernetes server version") - os.Exit(1) - } - exp := regexp.MustCompile(`v(\d*).(\d*).(\d*)`) - groups := exp.FindAllStringSubmatch(serverVersion.String(), -1) - if len(groups) != 1 || len(groups[0]) != 4 { - logger.Error(err, "Failed to extract kubernetes server version", "serverVersion", serverVersion) - os.Exit(1) - } - // convert string to int - // assuming the version are always intergers - major, err := strconv.Atoi(groups[0][1]) - if err != nil { - logger.Error(err, "Failed to extract kubernetes major server version", "serverVersion", serverVersion) - os.Exit(1) - } - minor, err := strconv.Atoi(groups[0][2]) - if err != nil { - logger.Error(err, "Failed to extract kubernetes minor server version", "serverVersion", serverVersion) - os.Exit(1) - } - sub, err := strconv.Atoi(groups[0][3]) - if err != nil { - logger.Error(err, "Failed to extract kubernetes sub minor server version", "serverVersion", serverVersion) - os.Exit(1) - } - if major <= 1 && minor <= 12 && sub < 7 { - logger.Info("Unsupported kubernetes server version %s. Kyverno is supported from version v1.12.7+", "serverVersion", serverVersion) - os.Exit(1) - } -} diff --git a/pkg/utils/util.go b/pkg/utils/util.go index 5fe0602e71..5766fe83f9 100644 --- a/pkg/utils/util.go +++ b/pkg/utils/util.go @@ -2,6 +2,8 @@ package utils import ( "reflect" + "regexp" + "strconv" "github.com/go-logr/logr" "github.com/minio/minio/pkg/wildcard" @@ -86,3 +88,40 @@ func CleanupOldCrd(client *dclient.Client, log logr.Logger) { } } } + +// CompareKubernetesVersion compare kuberneates client version to user given version +func CompareKubernetesVersion(client *client.Client, log logr.Logger, k8smajor, k8sminor, k8ssub int) bool { + logger := log.WithName("CompareKubernetesVersion") + serverVersion, err := client.DiscoveryClient.GetServerVersion() + if err != nil { + logger.Error(err, "Failed to get kubernetes server version") + return false + } + exp := regexp.MustCompile(`v(\d*).(\d*).(\d*)`) + groups := exp.FindAllStringSubmatch(serverVersion.String(), -1) + if len(groups) != 1 || len(groups[0]) != 4 { + logger.Error(err, "Failed to extract kubernetes server version", "serverVersion", serverVersion) + return false + } + // convert string to int + // assuming the version are always intergers + major, err := strconv.Atoi(groups[0][1]) + if err != nil { + logger.Error(err, "Failed to extract kubernetes major server version", "serverVersion", serverVersion) + return false + } + minor, err := strconv.Atoi(groups[0][2]) + if err != nil { + logger.Error(err, "Failed to extract kubernetes minor server version", "serverVersion", serverVersion) + return false + } + sub, err := strconv.Atoi(groups[0][3]) + if err != nil { + logger.Error(err, "Failed to extract kubernetes sub minor server version", "serverVersion", serverVersion) + return false + } + if major <= k8smajor && minor <= k8sminor && sub < k8ssub { + return false + } + return true +} diff --git a/pkg/webhookconfig/registration.go b/pkg/webhookconfig/registration.go index 32bd6a9dd0..1ed8d3b4e4 100644 --- a/pkg/webhookconfig/registration.go +++ b/pkg/webhookconfig/registration.go @@ -338,3 +338,8 @@ func (wrc *WebhookRegistrationClient) removePolicyValidatingWebhookConfiguration logger.V(4).Info("successfully deleted policy validating webhook configutation") } + +// GetWebhookTimeOut returns the value of webhook timeout +func (wrc *WebhookRegistrationClient) GetWebhookTimeOut() time.Duration { + return time.Duration(wrc.timeoutSeconds) +} diff --git a/pkg/webhooks/middleware.go b/pkg/webhooks/middleware.go new file mode 100644 index 0000000000..4fb44ae4f8 --- /dev/null +++ b/pkg/webhooks/middleware.go @@ -0,0 +1,15 @@ +package webhooks + +import ( + "net/http" + "time" +) + +func timeoutHandler(h http.Handler, timeout time.Duration) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var timeoutHandler http.Handler + msg := "ok" + timeoutHandler = http.TimeoutHandler(h, timeout*time.Second, msg) + timeoutHandler.ServeHTTP(w, r) + } +} diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index e04f7bb690..338d454e85 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -11,8 +11,8 @@ import ( "time" "github.com/julienschmidt/httprouter" - "github.com/nirmata/kyverno/pkg/openapi" + "github.com/nirmata/kyverno/pkg/utils" "github.com/go-logr/logr" "github.com/nirmata/kyverno/pkg/checker" @@ -131,12 +131,14 @@ func NewWebhookServer( log: log, openAPIController: openAPIController, } + mux := httprouter.New() - mux.HandlerFunc("POST", config.MutatingWebhookServicePath, ws.handlerFunc(ws.handleMutateAdmissionRequest, true)) - mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, ws.handlerFunc(ws.handleValidateAdmissionRequest, true)) - mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, ws.handlerFunc(ws.handlePolicyMutation, true)) - mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, ws.handlerFunc(ws.handlePolicyValidation, true)) - mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, ws.handlerFunc(ws.handleVerifyRequest, false)) + + mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handleValidateAdmissionRequest, true), ws.webhookRegistrationClient.GetWebhookTimeOut())) + mux.HandlerFunc("POST", config.MutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handleMutateAdmissionRequest, true), ws.webhookRegistrationClient.GetWebhookTimeOut())) + mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handlePolicyValidation, true), ws.webhookRegistrationClient.GetWebhookTimeOut())) + mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handlePolicyMutation, true), ws.webhookRegistrationClient.GetWebhookTimeOut())) + mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.handleVerifyRequest, false), ws.webhookRegistrationClient.GetWebhookTimeOut())) ws.server = http.Server{ Addr: ":443", // Listen on port for HTTPS requests TLSConfig: &tlsConfig, @@ -238,29 +240,37 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission } } - // MUTATION - // mutation failure should not block the resource creation - // any mutation failure is reported as the violation - patches := ws.HandleMutation(request, resource, policies, roles, clusterRoles) + var patches, patchedResource []byte - // patch the resource with patches before handling validation rules - patchedResource := processResourceWithPatches(patches, request.Object.Raw, logger) + versionCheck := utils.CompareKubernetesVersion(ws.client, ws.log, 1, 14, 0) - if ws.resourceWebhookWatcher != nil && ws.resourceWebhookWatcher.RunValidationInMutatingWebhook == "true" { - // VALIDATION - ok, msg := ws.HandleValidation(request, policies, patchedResource, roles, clusterRoles) - if !ok { - logger.Info("admission request denied") - return &v1beta1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Status: "Failure", - Message: msg, - }, + if versionCheck { + // MUTATION + // mutation failure should not block the resource creation + // any mutation failure is reported as the violation + patches = ws.HandleMutation(request, resource, policies, roles, clusterRoles) + + // patch the resource with patches before handling validation rules + patchedResource := processResourceWithPatches(patches, request.Object.Raw, logger) + + if ws.resourceWebhookWatcher != nil && ws.resourceWebhookWatcher.RunValidationInMutatingWebhook == "true" { + // VALIDATION + ok, msg := ws.HandleValidation(request, policies, patchedResource, roles, clusterRoles) + if !ok { + logger.Info("admission request denied") + return &v1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: "Failure", + Message: msg, + }, + } } } + } else { + // patch the resource with patches before handling validation rules + patchedResource = processResourceWithPatches(patches, request.Object.Raw, logger) } - // GENERATE // Only applied during resource creation // Success -> Generate Request CR created successsfully @@ -278,6 +288,7 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission } } } + // Succesfful processing of mutation & validation rules in policy patchType := v1beta1.PatchTypeJSONPatch return &v1beta1.AdmissionResponse{ @@ -288,6 +299,7 @@ func (ws *WebhookServer) handleMutateAdmissionRequest(request *v1beta1.Admission Patch: patches, PatchType: &patchType, } + } func (ws *WebhookServer) handleValidateAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { @@ -332,19 +344,22 @@ func (ws *WebhookServer) handleValidateAdmissionRequest(request *v1beta1.Admissi } } - // VALIDATION - ok, msg := ws.HandleValidation(request, policies, nil, roles, clusterRoles) - if !ok { - logger.Info("admission request denied") - return &v1beta1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Status: "Failure", - Message: msg, - }, + versionCheck := utils.CompareKubernetesVersion(ws.client, ws.log, 1, 14, 0) + + if !versionCheck { + // VALIDATION + ok, msg := ws.HandleValidation(request, policies, nil, roles, clusterRoles) + if !ok { + logger.Info("admission request denied") + return &v1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: "Failure", + Message: msg, + }, + } } } - return &v1beta1.AdmissionResponse{ Allowed: true, Result: &metav1.Status{