// Copyright 2020-2026 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 package generic import ( "context" "k8s.io/apimachinery/pkg/fields" "k8s.io/apiserver/pkg/authentication/serviceaccount" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api/meta" ad "github.com/projectcapsule/capsule/pkg/runtime/admission" "github.com/projectcapsule/capsule/pkg/runtime/events" "github.com/projectcapsule/capsule/pkg/runtime/gvk" "github.com/projectcapsule/capsule/pkg/runtime/handlers" "github.com/projectcapsule/capsule/pkg/runtime/indexers/tenantresource" ) type replicaHandler struct{} func ReplicaHandler() handlers.Handler { return &replicaHandler{} } func (h *replicaHandler) OnCreate( client.Client, client.Reader, admission.Decoder, events.EventRecorder, ) handlers.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } func (h *replicaHandler) OnDelete( c client.Client, reader client.Reader, _ admission.Decoder, recorder events.EventRecorder, ) handlers.Func { return func(ctx context.Context, req admission.Request) *admission.Response { return h.handler(ctx, c, req, recorder) } } func (h *replicaHandler) OnUpdate( c client.Client, _ client.Reader, _ admission.Decoder, recorder events.EventRecorder, ) handlers.Func { return func(ctx context.Context, req admission.Request) *admission.Response { return h.handler(ctx, c, req, recorder) } } func (h *replicaHandler) handler( ctx context.Context, c client.Client, req admission.Request, recorder events.EventRecorder, ) *admission.Response { // Checking if the object is managed by a TenantResource, local or global ref := gvk.ResourceID{ Group: req.Kind.Group, Version: req.Kind.Version, Kind: req.Kind.Kind, Name: req.Name, Namespace: req.Namespace, } gvkKey := ref.GetGVKKey("") global := &capsulev1beta2.GlobalTenantResourceList{} if err := c.List( ctx, global, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(tenantresource.CreatedIndexerFieldName, gvkKey), }, ); err != nil { return ad.ErroredResponse(err) } if len(global.Items) > 0 { for i := range global.Items { if isAllowedServiceAccount(req.UserInfo.Username, global.Items[i].Status.ServiceAccount) { return nil } } return ad.Denyf( "resource %s is managed by a global capsule replication %s", req.Name, global.Items[0].GetName(), ) } local := &capsulev1beta2.TenantResourceList{} if err := c.List( ctx, local, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(tenantresource.CreatedIndexerFieldName, gvkKey), }, ); err != nil { return ad.ErroredResponse(err) } if len(local.Items) > 0 { for i := range local.Items { if isAllowedServiceAccount(req.UserInfo.Username, local.Items[i].Status.ServiceAccount) { return nil } } return ad.Denyf( "resource %s is managed by a tenant capsule replication %s/%s", req.Name, local.Items[0].GetName(), local.Items[0].GetNamespace(), ) } return nil } func isAllowedServiceAccount(username string, sa *meta.NamespacedRFC1123ObjectReferenceWithNamespace) bool { if sa == nil { return false } ns, name, err := serviceaccount.SplitUsername(username) if err != nil { return false } return name == sa.Name.String() && ns == sa.Namespace.String() }