Skip to content

Commit

Permalink
add: Support the deletion protection of service and ingress
Browse files Browse the repository at this point in the history
Signed-off-by: kevin1689 <kevinyang1689@163.com>
  • Loading branch information
kevin1689-cloud committed Apr 28, 2023
1 parent 7f5046d commit 32d5ece
Show file tree
Hide file tree
Showing 8 changed files with 497 additions and 0 deletions.
25 changes: 25 additions & 0 deletions pkg/webhook/add_ingress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright 2021 The Kruise 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 (
"github.com/openkruise/kruise/pkg/webhook/ingress/validating"
)

func init() {
addHandlers(validating.HandlerMap)
}
25 changes: 25 additions & 0 deletions pkg/webhook/add_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright 2021 The Kruise 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 (
"github.com/openkruise/kruise/pkg/webhook/service/validating"
)

func init() {
addHandlers(validating.HandlerMap)
}
76 changes: 76 additions & 0 deletions pkg/webhook/ingress/validating/ingress_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright 2021 The Kruise 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 validating

import (
"context"
"net/http"

"github.com/openkruise/kruise/pkg/webhook/util/deletionprotection"

"k8s.io/klog/v2"

admissionv1 "k8s.io/api/admission/v1"
networkingv1 "k8s.io/api/networking/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

type IngressHandler struct {
Client client.Client

// Decoder decodes objects
Decoder *admission.Decoder
}

var _ admission.Handler = &IngressHandler{}

// Handle handles admission requests.
func (h *IngressHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
if req.AdmissionRequest.Operation != admissionv1.Delete || req.AdmissionRequest.SubResource != "" {
return admission.ValidationResponse(true, "")
}
if len(req.OldObject.Raw) == 0 {
klog.Warningf("Skip to validate ingress %s deletion for no old object, maybe because of Kubernetes version < 1.16", req.Name)
return admission.ValidationResponse(true, "")
}

obj := &networkingv1.Ingress{}
if err := h.Decoder.DecodeRaw(req.AdmissionRequest.OldObject, obj); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

if err := deletionprotection.ValidateIngressDeletion(h.Client, obj); err != nil {
return admission.Errored(http.StatusForbidden, err)
}
return admission.ValidationResponse(true, "")
}

var _ inject.Client = &IngressHandler{}

func (h *IngressHandler) InjectClient(c client.Client) error {
h.Client = c
return nil
}

var _ admission.DecoderInjector = &IngressHandler{}

func (h *IngressHandler) InjectDecoder(d *admission.Decoder) error {
h.Decoder = d
return nil
}
28 changes: 28 additions & 0 deletions pkg/webhook/ingress/validating/webhooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright 2021 The Kruise 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 validating

import "sigs.k8s.io/controller-runtime/pkg/webhook/admission"

// +kubebuilder:webhook:path=/validate-ingress,mutating=false,failurePolicy=fail,sideEffects=None,admissionReviewVersions=v1;v1beta1,groups=networking.k8s.io,resources=ingresses,verbs=delete,versions=v1,name=vingress.kb.io

var (
// HandlerMap contains admission webhook handlers
HandlerMap = map[string]admission.Handler{
"validate-ingress": &IngressHandler{},
}
)
76 changes: 76 additions & 0 deletions pkg/webhook/service/validating/service_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright 2021 The Kruise 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 validating

import (
"context"
"net/http"

"github.com/openkruise/kruise/pkg/webhook/util/deletionprotection"

"k8s.io/klog/v2"

admissionv1 "k8s.io/api/admission/v1"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

type ServiceHandler struct {
Client client.Client

// Decoder decodes objects
Decoder *admission.Decoder
}

var _ admission.Handler = &ServiceHandler{}

// Handle handles admission requests.
func (h *ServiceHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
if req.AdmissionRequest.Operation != admissionv1.Delete || req.AdmissionRequest.SubResource != "" {
return admission.ValidationResponse(true, "")
}
if len(req.OldObject.Raw) == 0 {
klog.Warningf("Skip to validate service %s deletion for no old object, maybe because of Kubernetes version < 1.16", req.Name)
return admission.ValidationResponse(true, "")
}

obj := &v1.Service{}
if err := h.Decoder.DecodeRaw(req.AdmissionRequest.OldObject, obj); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

if err := deletionprotection.ValidateServiceDeletion(h.Client, obj); err != nil {
return admission.Errored(http.StatusForbidden, err)
}
return admission.ValidationResponse(true, "")
}

var _ inject.Client = &ServiceHandler{}

func (h *ServiceHandler) InjectClient(c client.Client) error {
h.Client = c
return nil
}

var _ admission.DecoderInjector = &ServiceHandler{}

func (h *ServiceHandler) InjectDecoder(d *admission.Decoder) error {
h.Decoder = d
return nil
}
28 changes: 28 additions & 0 deletions pkg/webhook/service/validating/webhooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright 2021 The Kruise 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 validating

import "sigs.k8s.io/controller-runtime/pkg/webhook/admission"

// +kubebuilder:webhook:path=/validate-service,mutating=false,failurePolicy=fail,sideEffects=None,admissionReviewVersions=v1;v1beta1,groups="",resources=services,verbs=delete,versions=v1,name=vservice.kb.io

var (
// HandlerMap contains admission webhook handlers
HandlerMap = map[string]admission.Handler{
"validate-service": &ServiceHandler{},
}
)
69 changes: 69 additions & 0 deletions pkg/webhook/util/deletionprotection/deletion_protection.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import (

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"

v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
kubecontroller "k8s.io/kubernetes/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/client"

Expand All @@ -50,6 +52,73 @@ func ValidateWorkloadDeletion(obj metav1.Object, replicas *int32) error {
return nil
}

func ValidateServiceDeletion(c client.Client, service *v1.Service) error {
if !utilfeature.DefaultFeatureGate.Enabled(features.ResourcesDeletionProtection) || service.DeletionTimestamp != nil {
return nil
}
switch val := service.Labels[policyv1alpha1.DeletionProtectionKey]; val {
case policyv1alpha1.DeletionProtectionTypeAlways:
return fmt.Errorf("forbidden by ResourcesProtectionDeletion for %s=%s", policyv1alpha1.DeletionProtectionKey, val)
case policyv1alpha1.DeletionProtectionTypeCascading:
endpoints := v1.Endpoints{}
if err := c.Get(context.TODO(), types.NamespacedName{Namespace: service.Namespace, Name: service.Name}, &endpoints); err != nil {
return fmt.Errorf("forbidden by ResourcesProtectionDeletion for get endpoints error: %v", err)
}
var ipCount int
for _, subset := range endpoints.Subsets {
for _, address := range subset.Addresses {
if address.IP != "" {
ipCount++
}
}
}
if ipCount > 0 {
return fmt.Errorf("forbidden by ResourcesProtectionDeletion for %s=%s and endpoints ip count %d>0", policyv1alpha1.DeletionProtectionKey, val, ipCount)
}
default:
}
return nil
}

func ValidateIngressDeletion(c client.Client, ingress *networkingv1.Ingress) error {
if !utilfeature.DefaultFeatureGate.Enabled(features.ResourcesDeletionProtection) || ingress.DeletionTimestamp != nil {
return nil
}
switch val := ingress.Labels[policyv1alpha1.DeletionProtectionKey]; val {
case policyv1alpha1.DeletionProtectionTypeAlways:
return fmt.Errorf("forbidden by ResourcesProtectionDeletion for %s=%s", policyv1alpha1.DeletionProtectionKey, val)
case policyv1alpha1.DeletionProtectionTypeCascading:
var serviceName []string
for _, rule := range ingress.Spec.Rules {
for _, path := range rule.HTTP.Paths {
if err := c.Get(context.TODO(), types.NamespacedName{Namespace: ingress.Namespace, Name: path.Backend.Service.Name}, &v1.Service{}); err == nil {
serviceName = append(serviceName, path.Backend.Service.Name)
}
}
}

endpoints := v1.Endpoints{}
var ipCount int
for i := range serviceName {
if err := c.Get(context.TODO(), types.NamespacedName{Namespace: ingress.Namespace, Name: serviceName[i]}, &endpoints); err != nil {
return fmt.Errorf("forbidden by ResourcesProtectionDeletion for get endpoints error: %v", err)
}
for _, subset := range endpoints.Subsets {
for _, address := range subset.Addresses {
if address.IP != "" {
ipCount++
}
}
}
}
if ipCount > 0 {
return fmt.Errorf("forbidden by ResourcesProtectionDeletion for %s=%s and endpoints ip count %d>0", policyv1alpha1.DeletionProtectionKey, val, ipCount)
}
default:
}
return nil
}

func ValidateNamespaceDeletion(c client.Client, namespace *v1.Namespace) error {
if !utilfeature.DefaultFeatureGate.Enabled(features.ResourcesDeletionProtection) || namespace.DeletionTimestamp != nil {
return nil
Expand Down
Loading

0 comments on commit 32d5ece

Please sign in to comment.