feat: add resource protection webhook (#2168)
Signed-off-by: hongming <coder.scala@gmail.com>
This commit is contained in:
@@ -40,6 +40,7 @@ import (
|
|||||||
"kubesphere.io/kubesphere/pkg/controller/loginrecord"
|
"kubesphere.io/kubesphere/pkg/controller/loginrecord"
|
||||||
"kubesphere.io/kubesphere/pkg/controller/namespace"
|
"kubesphere.io/kubesphere/pkg/controller/namespace"
|
||||||
"kubesphere.io/kubesphere/pkg/controller/quota"
|
"kubesphere.io/kubesphere/pkg/controller/quota"
|
||||||
|
"kubesphere.io/kubesphere/pkg/controller/resourceprotection"
|
||||||
"kubesphere.io/kubesphere/pkg/controller/role"
|
"kubesphere.io/kubesphere/pkg/controller/role"
|
||||||
"kubesphere.io/kubesphere/pkg/controller/rolebinding"
|
"kubesphere.io/kubesphere/pkg/controller/rolebinding"
|
||||||
"kubesphere.io/kubesphere/pkg/controller/roletemplate"
|
"kubesphere.io/kubesphere/pkg/controller/roletemplate"
|
||||||
@@ -120,6 +121,7 @@ func init() {
|
|||||||
// kubectl
|
// kubectl
|
||||||
runtime.Must(controller.Register(&kubectl.Reconciler{}))
|
runtime.Must(controller.Register(&kubectl.Reconciler{}))
|
||||||
runtime.Must(controller.Register(&serviceaccounttoken.Reconciler{}))
|
runtime.Must(controller.Register(&serviceaccounttoken.Reconciler{}))
|
||||||
|
runtime.Must(controller.Register(&resourceprotection.Webhook{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControllerManagerCommand() *cobra.Command {
|
func NewControllerManagerCommand() *cobra.Command {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ spec:
|
|||||||
do
|
do
|
||||||
kubectl label ns $ns kubesphere.io/workspace=system-workspace
|
kubectl label ns $ns kubesphere.io/workspace=system-workspace
|
||||||
kubectl label ns $ns kubesphere.io/managed=true
|
kubectl label ns $ns kubesphere.io/managed=true
|
||||||
|
kubectl label ns $ns kubesphere.io/protected-resource=true
|
||||||
done
|
done
|
||||||
kubectl get ns -l 'kubesphere.io/workspace,!kubesphere.io/managed' --no-headers -o custom-columns=NAME:.metadata.name | \
|
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
|
xargs -I {} kubectl label ns {} kubesphere.io/managed=true
|
||||||
|
|||||||
@@ -360,6 +360,51 @@ webhooks:
|
|||||||
sideEffects: None
|
sideEffects: None
|
||||||
timeoutSeconds: 30
|
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" }}
|
{{- if eq (include "multicluster.role" .) "host" }}
|
||||||
---
|
---
|
||||||
apiVersion: admissionregistration.k8s.io/v1
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
apiVersion: tenant.kubesphere.io/v1beta1
|
apiVersion: tenant.kubesphere.io/v1beta1
|
||||||
kind: WorkspaceTemplate
|
kind: WorkspaceTemplate
|
||||||
metadata:
|
metadata:
|
||||||
|
labels:
|
||||||
|
kubesphere.io/protected-resource: 'true'
|
||||||
annotations:
|
annotations:
|
||||||
kubesphere.io/creator: admin
|
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."
|
kubesphere.io/description: "system-workspace is a built-in workspace automatically created by KubeSphere. It contains all system components to run KubeSphere."
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package constants
|
|||||||
import corev1 "k8s.io/api/core/v1"
|
import corev1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
SystemWorkspace = "system-workspace"
|
||||||
KubeSystemNamespace = "kube-system"
|
KubeSystemNamespace = "kube-system"
|
||||||
KubeSphereNamespace = "kubesphere-system"
|
KubeSphereNamespace = "kubesphere-system"
|
||||||
KubeSphereAPIServerName = "ks-apiserver"
|
KubeSphereAPIServerName = "ks-apiserver"
|
||||||
@@ -15,6 +16,7 @@ const (
|
|||||||
KubeSphereConfigMapDataKey = "kubesphere.yaml"
|
KubeSphereConfigMapDataKey = "kubesphere.yaml"
|
||||||
KubectlPodNamePrefix = "ks-managed-kubectl"
|
KubectlPodNamePrefix = "ks-managed-kubectl"
|
||||||
|
|
||||||
|
ProtectedResourceLabel = "kubesphere.io/protected-resource"
|
||||||
WorkspaceLabelKey = "kubesphere.io/workspace"
|
WorkspaceLabelKey = "kubesphere.io/workspace"
|
||||||
DisplayNameAnnotationKey = "kubesphere.io/alias-name"
|
DisplayNameAnnotationKey = "kubesphere.io/alias-name"
|
||||||
DescriptionAnnotationKey = "kubesphere.io/description"
|
DescriptionAnnotationKey = "kubesphere.io/description"
|
||||||
|
|||||||
@@ -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("")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user