From 23564f8e40a4b10a320dea05be7d19b379bd3f9a Mon Sep 17 00:00:00 2001 From: Maksim Fedotov Date: Mon, 16 May 2022 20:13:18 +0300 Subject: [PATCH] feat: protected tenant annotation --- api/v1beta1/tenant_annotations.go | 1 + e2e/tenant_protected_webhook_test.go | 40 +++++++++++++++++++++ main.go | 2 +- pkg/webhook/tenant/protected.go | 52 ++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 e2e/tenant_protected_webhook_test.go create mode 100644 pkg/webhook/tenant/protected.go diff --git a/api/v1beta1/tenant_annotations.go b/api/v1beta1/tenant_annotations.go index 31e5a5fa..9c7f3ad7 100644 --- a/api/v1beta1/tenant_annotations.go +++ b/api/v1beta1/tenant_annotations.go @@ -19,6 +19,7 @@ const ( ForbiddenNamespaceLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-labels-regexp" ForbiddenNamespaceAnnotationsAnnotation = "capsule.clastix.io/forbidden-namespace-annotations" ForbiddenNamespaceAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-annotations-regexp" + ProtectedTenantAnnotation = "capsule.clastix.io/protected" ) func UsedQuotaFor(resource fmt.Stringer) string { diff --git a/e2e/tenant_protected_webhook_test.go b/e2e/tenant_protected_webhook_test.go new file mode 100644 index 00000000..3ad33247 --- /dev/null +++ b/e2e/tenant_protected_webhook_test.go @@ -0,0 +1,40 @@ +//go:build e2e + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" +) + +var _ = Describe("Deleting a tenant with protected annotation", func() { + tnt := &capsulev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "protected_tenant", + Annotations: map[string]string{ + capsulev1beta1.ProtectedTenantAnnotation: "", + }, + }, + Spec: capsulev1beta1.TenantSpec{ + Owners: capsulev1beta1.OwnerListSpec{ + { + Name: "john", + Kind: "User", + }, + }, + }, + } + + It("should fail", func() { + Expect(k8sClient.Create(context.TODO(), tnt)).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), tnt)).ShouldNot(Succeed()) + }) +}) diff --git a/main.go b/main.go index 03ec791d..0c73f54e 100644 --- a/main.go +++ b/main.go @@ -217,7 +217,7 @@ func main() { route.PVC(pvc.Handler()), route.Service(service.Handler()), route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())), - route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler()), + route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler(), tenant.ProtectedHandler()), route.OwnerReference(utils.InCapsuleGroups(cfg, ownerreference.Handler(cfg))), route.Cordoning(tenant.CordoningHandler(cfg), tenant.ResourceCounterHandler()), route.Node(utils.InCapsuleGroups(cfg, node.UserMetadataHandler(cfg, kubeVersion))), diff --git a/pkg/webhook/tenant/protected.go b/pkg/webhook/tenant/protected.go new file mode 100644 index 00000000..a39dd7e7 --- /dev/null +++ b/pkg/webhook/tenant/protected.go @@ -0,0 +1,52 @@ +// Copyright 2020-2022 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + "context" + "fmt" + + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulewebhook "github.com/clastix/capsule/pkg/webhook" + "github.com/clastix/capsule/pkg/webhook/utils" +) + +type protectedHandler struct{} + +func ProtectedHandler() capsulewebhook.Handler { + return &protectedHandler{} +} + +func (h *protectedHandler) OnCreate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} + +func (h *protectedHandler) OnDelete(_ client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + tenant := &capsulev1beta1.Tenant{} + if err := decoder.Decode(req, tenant); err != nil { + return utils.ErroredResponse(err) + } + + if _, protected := tenant.Annotations[capsulev1beta1.ProtectedTenantAnnotation]; protected { + response := admission.Denied(fmt.Sprintf("tenant is protected and cannot be deleted, remove %s annotation before proceeding", capsulev1beta1.ProtectedTenantAnnotation)) + + return &response + } + + return nil + } +} + +func (h *protectedHandler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +}