diff --git a/controllers/tenant/rolebindings.go b/controllers/tenant/rolebindings.go index 06905ce6..f55140d2 100644 --- a/controllers/tenant/rolebindings.go +++ b/controllers/tenant/rolebindings.go @@ -97,6 +97,9 @@ func (r *Manager) syncAdditionalRoleBinding(tenant *capsulev1beta1.Tenant, ns st r.Log.Error(err, "Cannot sync Additional RoleBinding") } r.Log.Info(fmt.Sprintf("Additional RoleBindings sync result: %s", string(res)), "name", target.Name, "namespace", target.Namespace) + if err != nil { + return + } } return nil diff --git a/main.go b/main.go index 394ebdcf..2dcaaae1 100644 --- a/main.go +++ b/main.go @@ -167,7 +167,7 @@ func main() { route.PVC(pvc.Handler()), route.Service(service.Handler()), route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())), - route.Tenant(tenant.NameHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler()), + route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler()), route.OwnerReference(utils.InCapsuleGroups(cfg, ownerreference.Handler(cfg))), route.Cordoning(tenant.CordoningHandler(cfg)), route.Node(utils.InCapsuleGroups(cfg, node.UserMetadataHandler(cfg, kubeVersion))), diff --git a/pkg/webhook/tenant/rolebindings_regex.go b/pkg/webhook/tenant/rolebindings_regex.go new file mode 100644 index 00000000..c09edf7c --- /dev/null +++ b/pkg/webhook/tenant/rolebindings_regex.go @@ -0,0 +1,66 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + "context" + "fmt" + "strings" + + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/util/validation" + "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 rbRegexHandler struct { +} + +func RoleBindingRegexHandler() capsulewebhook.Handler { + return &rbRegexHandler{} +} + +func (h *rbRegexHandler) validate(req admission.Request, decoder *admission.Decoder) *admission.Response { + tenant := &capsulev1beta1.Tenant{} + if err := decoder.Decode(req, tenant); err != nil { + return utils.ErroredResponse(err) + } + + if len(tenant.Spec.AdditionalRoleBindings) > 0 { + for _, binding := range tenant.Spec.AdditionalRoleBindings { + for _, subject := range binding.Subjects { + if subject.Kind == rbacv1.ServiceAccountKind { + err := validation.IsDNS1123Subdomain(subject.Name) + if len(err) > 0 { + response := admission.Denied(fmt.Sprintf("Subject Name '%v' for binding '%v' is invalid. %v", subject.Name, binding.ClusterRoleName, strings.Join(err, ", "))) + return &response + } + } + } + } + } + return nil +} +func (h *rbRegexHandler) OnCreate(_ client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func { + return func(_ context.Context, req admission.Request) *admission.Response { + return h.validate(req, decoder) + } +} + +func (h *rbRegexHandler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} + +func (h *rbRegexHandler) OnUpdate(_ client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func { + return func(_ context.Context, req admission.Request) *admission.Response { + return h.validate(req, decoder) + } +}