From cac292082793b17f004320475e72e523632c841c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Mon, 6 Jun 2022 18:42:45 +0200 Subject: [PATCH] feat: grant global patch privileges and add patch handler --- api/v1beta1/zz_generated.deepcopy.go | 15 +++++ controllers/rbac/const.go | 2 +- go.mod | 2 +- go.sum | 2 + main.go | 2 +- pkg/webhook/namespace/patch.go | 82 ++++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 pkg/webhook/namespace/patch.go diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 1ea0ee53..f410020d 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -269,6 +269,21 @@ func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NonLimitedResourceError) DeepCopyInto(out *NonLimitedResourceError) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NonLimitedResourceError. +func (in *NonLimitedResourceError) DeepCopy() *NonLimitedResourceError { + if in == nil { + return nil + } + out := new(NonLimitedResourceError) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in OwnerListSpec) DeepCopyInto(out *OwnerListSpec) { { diff --git a/controllers/rbac/const.go b/controllers/rbac/const.go index 57f4c96e..74b9bba5 100644 --- a/controllers/rbac/const.go +++ b/controllers/rbac/const.go @@ -23,7 +23,7 @@ var ( { APIGroups: []string{""}, Resources: []string{"namespaces"}, - Verbs: []string{"create"}, + Verbs: []string{"create", "patch"}, }, }, }, diff --git a/go.mod b/go.mod index bea0732d..16b7865c 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect diff --git a/go.sum b/go.sum index 84533334..06ade127 100644 --- a/go.sum +++ b/go.sum @@ -696,6 +696,8 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= diff --git a/main.go b/main.go index 724fabbe..f2570bf7 100644 --- a/main.go +++ b/main.go @@ -198,7 +198,7 @@ func main() { webhooksList := append( make([]webhook.Webhook, 0), route.Pod(pod.ImagePullPolicy(), pod.ContainerRegistry(), pod.PriorityClass()), - route.Namespace(utils.InCapsuleGroups(cfg, namespacewebhook.QuotaHandler(), namespacewebhook.FreezeHandler(cfg), namespacewebhook.PrefixHandler(cfg), namespacewebhook.UserMetadataHandler())), + route.Namespace(utils.InCapsuleGroups(cfg, namespacewebhook.PatchHandler(), namespacewebhook.QuotaHandler(), namespacewebhook.FreezeHandler(cfg), namespacewebhook.PrefixHandler(cfg), namespacewebhook.UserMetadataHandler())), route.Ingress(ingress.Class(cfg), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()), route.PVC(pvc.Handler()), route.Service(service.Handler()), diff --git a/pkg/webhook/namespace/patch.go b/pkg/webhook/namespace/patch.go new file mode 100644 index 00000000..47bc0c3e --- /dev/null +++ b/pkg/webhook/namespace/patch.go @@ -0,0 +1,82 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 +package namespace + +import ( + "context" + "fmt" + "net/http" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "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 patchHandler struct{} + +func PatchHandler() capsulewebhook.Handler { + return &patchHandler{} +} + +func (r *patchHandler) OnCreate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} + +func (r *patchHandler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} + +func (r *patchHandler) OnUpdate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + // Decode Namespace + ns := &corev1.Namespace{} + if err := decoder.DecodeRaw(req.OldObject, ns); err != nil { + return utils.ErroredResponse(err) + } + + // Get Tenant Label + ln, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + if err != nil { + response := admission.Errored(http.StatusBadRequest, err) + + return &response + } + + // Extract Tenant from namespace + e := fmt.Sprintf("namespace/%s can not be patched", ns.Name) + + if label, ok := ns.ObjectMeta.Labels[ln]; ok { + // retrieving the selected Tenant + tnt := &capsulev1beta1.Tenant{} + if err = c.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil { + response := admission.Errored(http.StatusBadRequest, err) + + return &response + } + + if !utils.IsTenantOwner(tnt.Spec.Owners, req.UserInfo) { + recorder.Eventf(tnt, corev1.EventTypeWarning, "NamespacePatch", e) + response := admission.Denied(e) + + return &response + } + } else { + recorder.Eventf(ns, corev1.EventTypeWarning, "NamespacePatch", e) + response := admission.Denied(e) + + return &response + } + + return nil + } +}