Skip to content

Commit

Permalink
feat(webhook): add validation for namspace delete requests (#1754)
Browse files Browse the repository at this point in the history
  This PR adds validation on deletion of openebs namespace
  to avoid
  - loss of data in case of accidental deletion of
    namespace
  - stale resources with finalizers in openebs namespace which
    may be stuck on namespace deletion.

Signed-off-by: shubham <[email protected]>
  • Loading branch information
shubham14bajpai authored and kmova committed Sep 14, 2020
1 parent d3d3765 commit 7375c73
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelogs/unreleased/1754-shubham14bajpai
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feat(webhook): add validation for namspace delete requests
9 changes: 8 additions & 1 deletion cmd/admission-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/klog"

clientset "github.com/openebs/maya/pkg/client/generated/clientset/versioned"
ndmclientset "github.com/openebs/maya/pkg/client/generated/openebs.io/ndm/v1alpha1/clientset/internalclientset"
snapclientset "github.com/openebs/maya/pkg/client/generated/openebs.io/snapshot/v1/clientset/internalclientset"
)

Expand Down Expand Up @@ -75,6 +76,12 @@ func main() {
klog.Fatalf("Error building openebs snapshot clientset: %s", err.Error())
}

// Building NDM Clientset
ndmClient, err := ndmclientset.NewForConfig(cfg)
if err != nil {
klog.Fatalf("Error building ndm clientset: %s", err.Error())
}

// Fetch a reference to the admission server deployment object
ownerReference, err := webhook.GetAdmissionReference()
if err != nil {
Expand All @@ -85,7 +92,7 @@ func main() {
klog.Fatal(validatorErr, "failed to initialize validation server")
}

wh, err := webhook.New(parameters, kubeClient, openebsClient, snapClient)
wh, err := webhook.New(parameters, kubeClient, openebsClient, snapClient, ndmClient)
if err != nil {
klog.Fatalf("failed to create validation webhook: %s", err.Error())
}
Expand Down
18 changes: 18 additions & 0 deletions pkg/webhook/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ var (
addCSPCDeleteRule,
addCVCWithUpdateRule,
addSPCWithDeleteRule,
addNSWithDeleteRule,
}
cvcRuleWithOperations = v1beta1.RuleWithOperations{
Operations: []v1beta1.OperationType{
Expand All @@ -102,6 +103,16 @@ var (
Resources: []string{"storagepoolclaims"},
},
}
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
Expand Down Expand Up @@ -214,6 +225,7 @@ func createValidatingWebhookConfig(
},
cvcRuleWithOperations,
spcRuleWithOperations,
nsRuleWithOperations,
},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Expand Down Expand Up @@ -507,6 +519,12 @@ func addSPCWithDeleteRule(config *v1beta1.ValidatingWebhookConfiguration) {
}
}

func addNSWithDeleteRule(config *v1beta1.ValidatingWebhookConfiguration) {
if util.IsCurrentLessThanNewVersion(config.Labels[string(apis.OpenEBSVersionKey)], "2.1.0") {
config.Webhooks[0].Rules = append(config.Webhooks[0].Rules, nsRuleWithOperations)
}
}

func getOldService(openebsNamespace string) (*corev1.ServiceList, error) {
v100SVCName := "admission-server-svc"
// fetch service 1.1.0 onwards based on label
Expand Down
76 changes: 75 additions & 1 deletion pkg/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
snapshot "github.com/openebs/maya/pkg/apis/openebs.io/snapshot/v1"
"github.com/openebs/maya/pkg/apis/openebs.io/v1alpha1"
clientset "github.com/openebs/maya/pkg/client/generated/clientset/versioned"
ndmclientset "github.com/openebs/maya/pkg/client/generated/openebs.io/ndm/v1alpha1/clientset/internalclientset"
snapclient "github.com/openebs/maya/pkg/client/generated/openebs.io/snapshot/v1/clientset/internalclientset"
"github.com/pkg/errors"
"k8s.io/api/admission/v1beta1"
Expand Down Expand Up @@ -80,6 +81,8 @@ type webhook struct {

// snapClientSet is a snaphot custom resource package generated from custom API group.
snapClientSet snapclient.Interface

ndmClientset ndmclientset.Interface
}

// Parameters are server configures parameters
Expand All @@ -105,7 +108,8 @@ func init() {
// set up secret (for TLS certs) k8s resource. This function runs forever.
func New(p Parameters, kubeClient kubernetes.Interface,
openebsClient clientset.Interface,
snapClient snapclient.Interface) (
snapClient snapclient.Interface,
ndmClient ndmclientset.Interface) (
*webhook, error) {

admNamespace, err := getOpenebsNamespace()
Expand Down Expand Up @@ -173,6 +177,7 @@ func New(p Parameters, kubeClient kubernetes.Interface,
kubeClient: kubeClient,
clientset: openebsClient,
snapClientSet: snapClient,
ndmClientset: ndmClient,
}
return wh, nil
}
Expand Down Expand Up @@ -421,6 +426,9 @@ func (wh *webhook) validate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionRespo
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)
Expand Down Expand Up @@ -453,6 +461,72 @@ func (wh *webhook) validatePVC(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionRe
return response
}

func (wh *webhook) validateNamespace(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
req := ar.Request
response := &v1beta1.AdmissionResponse{}
response.Allowed = true
// validates only if requested operation is DELETE
if 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.ndmClientset.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
}
return response
}

// getCstorVolumes gets the list of CstorVolumes based in the source-volume labels
func (wh *webhook) getCstorVolumes(listOptions metav1.ListOptions) (*v1alpha1.CStorVolumeList, error) {
return wh.clientset.OpenebsV1alpha1().CStorVolumes("").List(listOptions)
Expand Down

0 comments on commit 7375c73

Please sign in to comment.