feat: add resource protection webhook (#2168)

Signed-off-by: hongming <coder.scala@gmail.com>
This commit is contained in:
hongming
2025-03-19 12:31:33 +08:00
committed by ks-ci-bot
parent 9fab44d0bf
commit 447bc08639
6 changed files with 108 additions and 0 deletions

View File

@@ -40,6 +40,7 @@ import (
"kubesphere.io/kubesphere/pkg/controller/loginrecord"
"kubesphere.io/kubesphere/pkg/controller/namespace"
"kubesphere.io/kubesphere/pkg/controller/quota"
"kubesphere.io/kubesphere/pkg/controller/resourceprotection"
"kubesphere.io/kubesphere/pkg/controller/role"
"kubesphere.io/kubesphere/pkg/controller/rolebinding"
"kubesphere.io/kubesphere/pkg/controller/roletemplate"
@@ -120,6 +121,7 @@ func init() {
// kubectl
runtime.Must(controller.Register(&kubectl.Reconciler{}))
runtime.Must(controller.Register(&serviceaccounttoken.Reconciler{}))
runtime.Must(controller.Register(&resourceprotection.Webhook{}))
}
func NewControllerManagerCommand() *cobra.Command {

View File

@@ -26,6 +26,7 @@ spec:
do
kubectl label ns $ns kubesphere.io/workspace=system-workspace
kubectl label ns $ns kubesphere.io/managed=true
kubectl label ns $ns kubesphere.io/protected-resource=true
done
kubectl get ns -l 'kubesphere.io/workspace,!kubesphere.io/managed' --no-headers -o custom-columns=NAME:.metadata.name | \
xargs -I {} kubectl label ns {} kubesphere.io/managed=true

View File

@@ -360,6 +360,51 @@ webhooks:
sideEffects: None
timeoutSeconds: 30
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: protector.kubesphere.io
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
caBundle: {{ b64enc $ca.Cert | quote }}
service:
name: ks-controller-manager
namespace: {{ .Release.Namespace }}
path: /resource-protector
port: 443
failurePolicy: Ignore
matchPolicy: Exact
name: protector.kubesphere.io
namespaceSelector: {}
objectSelector:
matchExpressions:
- key: kubesphere.io/protected-resource
operator: Exists
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- DELETE
resources:
- namespaces
scope: '*'
- apiGroups:
- "tenant.kubesphere.io"
apiVersions:
- v1beta1
operations:
- DELETE
resources:
- workspacetemplates
scope: '*'
sideEffects: None
timeoutSeconds: 30
{{- if eq (include "multicluster.role" .) "host" }}
---
apiVersion: admissionregistration.k8s.io/v1

View File

@@ -2,6 +2,8 @@
apiVersion: tenant.kubesphere.io/v1beta1
kind: WorkspaceTemplate
metadata:
labels:
kubesphere.io/protected-resource: 'true'
annotations:
kubesphere.io/creator: admin
kubesphere.io/description: "system-workspace is a built-in workspace automatically created by KubeSphere. It contains all system components to run KubeSphere."

View File

@@ -8,6 +8,7 @@ package constants
import corev1 "k8s.io/api/core/v1"
const (
SystemWorkspace = "system-workspace"
KubeSystemNamespace = "kube-system"
KubeSphereNamespace = "kubesphere-system"
KubeSphereAPIServerName = "ks-apiserver"
@@ -15,6 +16,7 @@ const (
KubeSphereConfigMapDataKey = "kubesphere.yaml"
KubectlPodNamePrefix = "ks-managed-kubectl"
ProtectedResourceLabel = "kubesphere.io/protected-resource"
WorkspaceLabelKey = "kubesphere.io/workspace"
DisplayNameAnnotationKey = "kubesphere.io/alias-name"
DescriptionAnnotationKey = "kubesphere.io/description"

View File

@@ -0,0 +1,56 @@
package resourceprotection
import (
"context"
"net/http"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"kubesphere.io/kubesphere/pkg/constants"
kscontroller "kubesphere.io/kubesphere/pkg/controller"
)
const webhookName = "resource-protection-webhook"
func (w *Webhook) Name() string {
return webhookName
}
type Webhook struct {
client.Client
}
func (w *Webhook) SetupWithManager(mgr *kscontroller.Manager) error {
w.Client = mgr.GetClient()
mgr.GetWebhookServer().Register("/resource-protector", &webhook.Admission{Handler: w})
return nil
}
func (w *Webhook) Handle(ctx context.Context, req admission.Request) admission.Response {
if req.Operation == admissionv1.Delete {
gvr := req.RequestResource
gvk, err := w.RESTMapper().KindFor(schema.GroupVersionResource{
Group: gvr.Group,
Version: gvr.Version,
Resource: gvr.Resource,
})
if err != nil {
return webhook.Errored(http.StatusInternalServerError, err)
}
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(gvk)
if err = w.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: req.Name}, obj); err != nil {
return webhook.Errored(http.StatusInternalServerError, err)
}
if obj.GetLabels()[constants.ProtectedResourceLabel] == "true" {
return webhook.Denied("this resource may not be deleted")
}
}
return admission.Allowed("")
}