From 9426a68e78bc3f767c974a7ff6fd0372c9483ede Mon Sep 17 00:00:00 2001 From: Prateek Pandey Date: Thu, 11 Feb 2021 12:17:41 +0530 Subject: [PATCH 1/2] fix(webhook): cleanup validatingconfig on openebs namespace deletion (#246) from k8s 1.20+ onwards Cluster-scoped dependents can only specify cluster-scoped owners. In v1.20+, if a cluster-scoped dependent specifies a namespaced kind as an owner, it is treated as having an unresolveable owner reference, and is not able to be garbage collected. In v1.20+, if the garbage collector detects an invalid cross-namespace ownerReference, or a cluster-scoped dependent with an ownerReference referencing a namespaced kind, a warning Event with a reason of OwnerRefInvalidNamespace and an involvedObject of the invalid dependent is reported. You can check for that kind of Event by running kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace. Signed-off-by: prateekpandey14 --- pkg/webhook/configuration.go | 29 +++++++-- pkg/webhook/namespace.go | 111 +++++++++++++++++++++++++++++++++++ pkg/webhook/util.go | 21 ++++++- pkg/webhook/webhook.go | 6 +- 4 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 pkg/webhook/namespace.go diff --git a/pkg/webhook/configuration.go b/pkg/webhook/configuration.go index b1cfb5cd..107674cc 100644 --- a/pkg/webhook/configuration.go +++ b/pkg/webhook/configuration.go @@ -73,13 +73,17 @@ var ( Ignore = admissionregistration.Ignore // Fail means that an error calling the webhook causes the admission to fail. Fail = admissionregistration.Fail + // SideEffectClassNone means that calling the webhook will have no side effects. + SideEffectClassNone = v1beta1.SideEffectClassNone // WebhookFailurePolicye represents failure policy env name to make it configurable // via ENV WebhookFailurePolicy = "ADMISSION_WEBHOOK_FAILURE_POLICY" // transformation function lists to upgrade webhook resources - transformSecret = []transformSecretFunc{} - transformSvc = []transformSvcFunc{} - transformConfig = []transformConfigFunc{} + transformSecret = []transformSecretFunc{} + transformSvc = []transformSvcFunc{} + transformConfig = []transformConfigFunc{ + addNSWithDeleteRule, + } cvcRuleWithOperations = v1beta1.RuleWithOperations{ Operations: []v1beta1.OperationType{ v1beta1.Update, @@ -90,6 +94,16 @@ var ( Resources: []string{"cstorvolumeconfigs"}, }, } + nsRuleWithOperations = v1beta1.RuleWithOperations{ + Operations: []v1beta1.OperationType{ + v1beta1.Delete, + }, + Rule: v1beta1.Rule{ + APIGroups: []string{"*"}, + APIVersions: []string{"*"}, + Resources: []string{"namespaces"}, + }, + } ) // createWebhookService creates our webhook Service resource if it does not @@ -202,6 +216,7 @@ func (c *client) createAdmissionValidatingConfig( }, }, cvcRuleWithOperations, + nsRuleWithOperations, }, ClientConfig: admissionregistration.WebhookClientConfig{ Service: &admissionregistration.ServiceReference{ @@ -211,7 +226,7 @@ func (c *client) createAdmissionValidatingConfig( }, CABundle: signingCert, }, - // SideEffects: &sideEffectClass, + SideEffects: &SideEffectClassNone, // AdmissionReviewVersions: []string{"v1"}, TimeoutSeconds: &five, FailurePolicy: failurePolicy(), @@ -423,6 +438,12 @@ func getOpenebsNamespace() (string, error) { return ns, nil } +func addNSWithDeleteRule(config *v1beta1.ValidatingWebhookConfiguration) { + if IsCurrentLessThanNewVersion(config.Labels[string(types.OpenEBSVersionLabelKey)], "2.5.0") { + config.Webhooks[0].Rules = append(config.Webhooks[0].Rules, nsRuleWithOperations) + } +} + // GetAdmissionName return the admission server name func GetAdmissionName() (string, error) { admissionName, found := os.LookupEnv(AdmissionNameEnvVar) diff --git a/pkg/webhook/namespace.go b/pkg/webhook/namespace.go new file mode 100644 index 00000000..056b61d0 --- /dev/null +++ b/pkg/webhook/namespace.go @@ -0,0 +1,111 @@ +/* +Copyright 2021 The OpenEBS Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "fmt" + + "k8s.io/api/admission/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog" +) + +func (wh *webhook) validateNamespace(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { + req := ar.Request + response := &v1beta1.AdmissionResponse{} + response.Allowed = true + openebsNamespace, err := getOpenebsNamespace() + if err != nil { + response.Allowed = false + response.Result = &metav1.Status{ + Message: fmt.Sprintf("error getting OPENEBS_NAMESPACE env %s: %v", req.Name, err.Error()), + } + return response + } + // validates only if requested operation is DELETE + if openebsNamespace == req.Name && req.Operation == v1beta1.Delete { + return wh.validateNamespaceDeleteRequest(req) + } + klog.V(2).Info("Admission wehbook for Namespace module not " + + "configured for operations other than DELETE") + return response +} + +func (wh *webhook) validateNamespaceDeleteRequest(req *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { + response := &v1beta1.AdmissionResponse{} + response.Allowed = true + svcLabel := "openebs.io/controller-service=jiva-controller-svc" + + msg := fmt.Sprintf("either BDCs or services with the label %s exists in the namespace %s.", svcLabel, req.Name) + + // ignore the Delete request of Namespace if resource name is empty + if req.Name == "" { + return response + } + + bdcList, err := wh.clientset.OpenebsV1alpha1(). + BlockDeviceClaims(req.Name). + List(metav1.ListOptions{}) + if err != nil { + response.Allowed = false + response.Result = &metav1.Status{ + Message: fmt.Sprintf("error listing BDC in namespace %s: %v", req.Name, err.Error()), + } + return response + } + + if len(bdcList.Items) != 0 { + response.Allowed = false + response.Result = &metav1.Status{ + Message: msg, + } + return response + } + + svcList, err := wh.kubeClient.CoreV1().Services(req.Name). + List(metav1.ListOptions{ + LabelSelector: svcLabel, + }) + if err != nil { + response.Allowed = false + response.Result = &metav1.Status{ + Message: fmt.Sprintf("error listing svc in namespace %s: %v", req.Name, err.Error()), + } + return response + } + + if len(svcList.Items) != 0 { + response.Allowed = false + response.Result = &metav1.Status{ + Message: msg, + } + return response + } + // Delete the validatingWebhookConfiguration only if its a delete request to + // delete openebs namespace + err = wh.kubeClient.AdmissionregistrationV1(). + ValidatingWebhookConfigurations(). + Delete(validatorWebhook, &metav1.DeleteOptions{}) + if err != nil { + response.Allowed = false + response.Result = &metav1.Status{ + Message: err.Error(), + } + return response + } + return response +} diff --git a/pkg/webhook/util.go b/pkg/webhook/util.go index 37aae7c6..0f14cf0c 100644 --- a/pkg/webhook/util.go +++ b/pkg/webhook/util.go @@ -14,7 +14,11 @@ package webhook -import "fmt" +import ( + "fmt" + "strconv" + "strings" +) const ( unit = 1024 @@ -33,3 +37,18 @@ func ByteCount(b uint64) string { return fmt.Sprintf("%d%c", uint64(b)/uint64(div), "KMGTPE"[index]) } + +// if currentversion is less `<` then new version (return true in case of equal version) +// TODO use version lib to properly handle versions https://github.com/hashicorp/go-version +func IsCurrentLessThanNewVersion(old, new string) bool { + oldVersions := strings.Split(strings.Split(old, "-")[0], ".") + newVersions := strings.Split(strings.Split(new, "-")[0], ".") + for i := 0; i < len(oldVersions); i++ { + oldVersion, _ := strconv.Atoi(oldVersions[i]) + newVersion, _ := strconv.Atoi(newVersions[i]) + if oldVersion > newVersion { + return false + } + } + return true +} diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index a0bc3f47..d2041486 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -182,13 +182,16 @@ func validationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool return required } -// validate validates the persistentvolumeclaim(PVC) create, delete request +// validate validates the different openebs resource related operations func (wh *webhook) validate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { req := ar.Request response := &v1beta1.AdmissionResponse{} response.Allowed = true klog.Info("Admission webhook request received") switch req.Kind.Kind { + case "Namespace": + klog.V(2).Infof("Admission webhook request for type %s", req.Kind.Kind) + return wh.validateNamespace(ar) case "PersistentVolumeClaim": klog.V(2).Infof("Admission webhook request for type %s", req.Kind.Kind) return wh.validatePVC(ar) @@ -198,6 +201,7 @@ func (wh *webhook) validate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionRespo case "CStorVolumeConfig": klog.V(2).Infof("Admission webhook request for type %s", req.Kind.Kind) return wh.validateCVC(ar) + default: klog.V(2).Infof("Admission webhook not configured for type %s", req.Kind.Kind) return response From e2c0fb61de69fcea647c00955473fc2ec2405b4f Mon Sep 17 00:00:00 2001 From: Prateek Pandey Date: Thu, 11 Feb 2021 13:04:45 +0530 Subject: [PATCH 2/2] refact(webhook): refactor namespace validation steps (#247) Signed-off-by: prateekpandey14 --- pkg/webhook/namespace.go | 44 +--------------------------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/pkg/webhook/namespace.go b/pkg/webhook/namespace.go index 056b61d0..4f5a8401 100644 --- a/pkg/webhook/namespace.go +++ b/pkg/webhook/namespace.go @@ -48,56 +48,14 @@ func (wh *webhook) validateNamespace(ar *v1beta1.AdmissionReview) *v1beta1.Admis func (wh *webhook) validateNamespaceDeleteRequest(req *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { response := &v1beta1.AdmissionResponse{} response.Allowed = true - svcLabel := "openebs.io/controller-service=jiva-controller-svc" - - msg := fmt.Sprintf("either BDCs or services with the label %s exists in the namespace %s.", svcLabel, req.Name) // ignore the Delete request of Namespace if resource name is empty if req.Name == "" { return response } - - bdcList, err := wh.clientset.OpenebsV1alpha1(). - BlockDeviceClaims(req.Name). - List(metav1.ListOptions{}) - if err != nil { - response.Allowed = false - response.Result = &metav1.Status{ - Message: fmt.Sprintf("error listing BDC in namespace %s: %v", req.Name, err.Error()), - } - return response - } - - if len(bdcList.Items) != 0 { - response.Allowed = false - response.Result = &metav1.Status{ - Message: msg, - } - return response - } - - svcList, err := wh.kubeClient.CoreV1().Services(req.Name). - List(metav1.ListOptions{ - LabelSelector: svcLabel, - }) - if err != nil { - response.Allowed = false - response.Result = &metav1.Status{ - Message: fmt.Sprintf("error listing svc in namespace %s: %v", req.Name, err.Error()), - } - return response - } - - if len(svcList.Items) != 0 { - response.Allowed = false - response.Result = &metav1.Status{ - Message: msg, - } - return response - } // Delete the validatingWebhookConfiguration only if its a delete request to // delete openebs namespace - err = wh.kubeClient.AdmissionregistrationV1(). + err := wh.kubeClient.AdmissionregistrationV1(). ValidatingWebhookConfigurations(). Delete(validatorWebhook, &metav1.DeleteOptions{}) if err != nil {